Home > Posts Dev frontend with prod backend

Max Heinritz

Dev frontend with prod backend

During frontend development, I like being able to connect directly to the production backend from development frontend. For example:

# dev frontend

# use a button in the dev frontend UI
# to toggle between either:

# (1) dev backend

# or (2) prod backend

Use cases

Using cookies

If you are logged in with a cookie-based session on the prod frontend, then a cookie set there can be sent by the browser even when requests are made from the dev frontend at a different URL – so long as SameSite is set to None.


The permissions and security of the production backend still apply when using the dev frontend. The developer is logged in as their production user and has the same permissions with the dev frontend as they would have with prod frontend.



The frontend implementation involves React Context:

import { noop } from "lodash";
import { createContext } from "react";

export const BackendUrlContext = createContext<{
  backendUrl: string | undefined;
  setBackendUrl: (backendUrl: string) => void;
  checkBackendUrl: () => void;
  // True if dev frontend talking to prod backend
  // or prod frontend talking to dev backend.
  isOtherEnvBackendUrl: boolean;
  backendUrl: undefined,
  setBackendUrl: noop,
  checkBackendUrl: noop,
  isOtherEnvBackendUrl: false,

And a React provider.

import { ReactNode, useEffect, useState } from "react";

import {
} from "src/common/constants/backend-url";
import { readCookie, writeCookie } from "src/common/cookie/cookie";
import { AppCookieName } from "src/common/cookie/cookie.registry";
import { isProdFrontend } from "src/common/util/env.util";

import { BackendUrlContext } from "./BackendUrlContext";

const BackendUrlProvider = ({ children }: { children: ReactNode }) => {
  const [backendUrl, setBackendUrlImpl] = useState<string | undefined>(
    isProdFrontend() ? PROD_BACKEND_URL : undefined

  const setBackendUrl = async (backendUrl: string) => {
    await writeCookie(AppCookieName.BACKEND_URL, { backendUrl });

  const checkBackendUrl = async () => {
    const wrapper = await readCookie(AppCookieName.BACKEND_URL);
    const backendUrl =
      wrapper?.backendUrl || DEFAULT_BACKEND_URL_FOR_FRONTEND_ENV;

  // Check the backend URL immediately upon page load.
  useEffect(() => {
  }, []);

  if (!backendUrl) {
    // Wait until the backend URL is set before loading the app.
    return null;

  const isOtherEnvBackendUrl =

  return (

export default BackendUrlProvider;

Then when building the Relay or Apollo environment, the context can be used to initialize the network to use that particular backend.

// ...
const { backendUrl } = useContext(BackendUrlContext);
// ...use the backendUrl to configure the GraphQL client.


The thing to watch out for on the backend is that cookies need to be HTTPS and have sameSite: 'none'.

  // We need "none" because the frontends make requests to api.* from
  // different domains.
  sameSite: 'none',