remix-run/react-router · error

You cannot use navigator.push() on the server because it is

Error message

You cannot use navigator.push() on the server because it is a stateless environment. This error was probably triggered when you did a `navigate(${JSON.stringify(to)})` somewhere in your app.

What it means

During SSR, the router runs on a stateless navigator whose push() throws (lib/dom/server.tsx:268): there is no history stack to push onto when rendering a response once. The message includes the offending destination so you can locate the navigate(to) call that ran on the server.

Source

Thrown at packages/react-router/lib/dom/server.tsx:297

        ...(val.name !== "Error"
          ? {
              __subType: val.name,
            }
          : {}),
      };
    } else {
      serialized[key] = val;
    }
  }
  return serialized;
}

function getStatelessNavigator() {
  return {
    createHref,
    encodeLocation,
    push(to: To) {
      throw new Error(
        `You cannot use navigator.push() on the server because it is a stateless ` +
          `environment. This error was probably triggered when you did a ` +
          `\`navigate(${JSON.stringify(to)})\` somewhere in your app.`,
      );
    },
    replace(to: To) {
      throw new Error(
        `You cannot use navigator.replace() on the server because it is a stateless ` +
          `environment. This error was probably triggered when you did a ` +
          `\`navigate(${JSON.stringify(to)}, { replace: true })\` somewhere ` +
          `in your app.`,
      );
    },
    go(delta: number) {
      throw new Error(
        `You cannot use navigator.go() on the server because it is a stateless ` +
          `environment. This error was probably triggered when you did a ` +
          `\`navigate(${delta})\` somewhere in your app.`,

View on GitHub (pinned to 7aea711dd1)

Solutions

  1. Do the redirect in the loader/action: throw redirect('/login')
  2. Move navigate() calls into event handlers or effects so they only run client-side
  3. Replace render-time <Navigate>/navigate with loader-level redirects for SSR-visible conditions

Example fix

// before
function Dashboard() {
  const navigate = useNavigate()
  if (!user) navigate('/login') // runs during SSR render
}
// after
export async function loader({ request }) {
  if (!await getUser(request)) throw redirect('/login')
}
Defensive patterns

Strategy: validation

Validate before calling

const isServer = typeof window === 'undefined';
// Redirect decisions that must work during SSR belong in loaders:
export async function loader({ request }: Route.LoaderArgs) {
  if (!(await getUser(request))) throw redirect('/login');
  return null;
}

Prevention

When it happens

Trigger: Calling navigate('/path') from useNavigate() during the server render of a component (not inside an effect/handler); invoking router.navigate() during SSR; navigation triggered synchronously in a component body or module scope while server rendering.

Common situations: Auth gating done by navigate() in a component instead of a loader redirect; login/onboarding flows calling navigate on mount during SSR; component-level side effects that assume a browser.

Related errors


AI-assisted analysis of remix-run/react-router@7aea711dd1 (2026-08-18). Data as JSON: /api/errors/62aecee54f841012. Report an issue: GitHub.