createBrowserRouter vs createMemoryRouter: Key Differences in React Router

Both createBrowserRouter and createMemoryRouter return fully-initialized DataRouter instances, but createBrowserRouter synchronizes with the browser's URL bar using the History API while createMemoryRouter maintains navigation state in a JavaScript array for testing and non-DOM environments.

When building applications with remix-run/react-router, selecting the appropriate router factory determines how your application handles navigation state, URL synchronization, and server-side rendering hydration. While both functions instantiate the same underlying createRouter core, they diverge significantly in history implementation, environment requirements, and configuration options.

History Implementation and Environment

The fundamental difference between these routers lies in how they manage the navigation stack.

Browser History vs Memory History

createBrowserRouter, implemented in packages/react-router/lib/dom/lib.tsx, utilizes createBrowserHistory to interface with the browser's native window.history API. This enables pushState and popstate event handling, ensuring the URL bar reflects the current route and supports native browser navigation features like back/forward buttons.

In contrast, createMemoryRouter, defined in packages/react-router/lib/components.tsx, employs createMemoryHistory which stores navigation entries in a simple JavaScript array. This approach completely decouples routing from the URL bar, making it ideal for environments where window is undefined or inaccessible.

Target Environments

createBrowserRouter is designed for:

  • Standard browser applications requiring URL synchronization
  • Server-side rendering (SSR) with client-side hydration
  • Production web apps where SEO and shareable URLs matter

createMemoryRouter excels in:

  • Unit and integration testing with deterministic navigation
  • React Native applications without browser history
  • Storybook and isolated component development
  • Server-only rendering without DOM access

Configuration Options and API Differences

Both routers accept common DataRouter options, but expose environment-specific configurations.

createBrowserRouter Options

Located in packages/react-router/lib/dom/lib.tsx (lines 645-663), createBrowserRouter accepts DOMRouterOpts including:

  • basename: The base URL for all locations
  • future: Future flags for v7 compatibility
  • window: Custom window object injection for SSR environments
  • hydrationData: Server-rendered state restored via parseHydrationData()
const router = createBrowserRouter(routes, {
  basename: "/app",
  future: { v7_partialHydration: true },
  hydrationData: window.__INITIAL_DATA__
});

createMemoryRouter Options

Found in packages/react-router/lib/components.tsx (lines 311-330), createMemoryRouter accepts MemoryRouterOpts:

  • initialEntries: Array of initial locations (defaults to ["/"])
  • initialIndex: Starting index in the entries array
const router = createMemoryRouter(routes, {
  initialEntries: ["/", "/dashboard", "/settings"],
  initialIndex: 1 // Start at /dashboard
});

Server-Side Rendering and Hydration

The handling of server-rendered markup represents a critical functional divergence.

createBrowserRouter implements hydration support through parseHydrationData(), automatically detecting and restoring server-rendered loader data and error boundaries. This makes it the default choice for Framework Mode applications using React Router's data APIs with SSR.

createMemoryRouter does not process hydration data. When mounted, it initializes a fresh memory history stack based solely on initialEntries. This behavior is intentional for testing scenarios where you want complete control over the initial state without inheriting server context.

Practical Implementation Examples

Browser Router for Production Apps

For standard web applications requiring URL synchronization and SSR support:

import { createBrowserRouter } from "react-router";
import { RouterProvider } from "react-router/dom";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      { index: true, element: <Home /> },
      { path: "about", element: <About /> }
    ]
  }
], {
  basename: "/app"
});

createRoot(document.getElementById("root")!).render(
  <RouterProvider router={router} />
);

Memory Router for Testing

For deterministic testing without browser dependencies:

import { createMemoryRouter } from "react-router";
import { RouterProvider } from "react-router/dom";
import { render, screen } from "@testing-library/react";

test("renders dashboard from initial entry", () => {
  const router = createMemoryRouter([
    { path: "/", element: <Home /> },
    { path: "/dashboard", element: <Dashboard /> }
  ], {
    initialEntries: ["/dashboard"]
  });

  render(<RouterProvider router={router} />);
  
  expect(screen.getByText("Dashboard")).toBeInTheDocument();
});

Summary

  • History Management: createBrowserRouter uses the browser's History API (createBrowserHistory) for URL synchronization, while createMemoryRouter uses an in-memory array (createMemoryHistory) with no URL changes.

  • Environment: Use createBrowserRouter for production browser apps and SSR hydration; use createMemoryRouter for testing, React Native, and non-DOM environments.

  • Configuration: createBrowserRouter accepts basename, window, and hydrationData for SSR; createMemoryRouter accepts initialEntries and initialIndex for deterministic state setup.

  • Implementation: Both instantiate the same createRouter core but differ in history implementation—packages/react-router/lib/dom/lib.tsx for browser, packages/react-router/lib/components.tsx for memory.

Frequently Asked Questions

When should I use createMemoryRouter over createBrowserRouter?

Use createMemoryRouter when your application runs in an environment without a real browser URL bar, such as unit tests, React Native mobile apps, or Node.js server rendering without DOM access. It provides deterministic navigation control through initialEntries, making it ideal for testing specific route states without side effects.

Does createMemoryRouter support server-side rendering?

While createMemoryRouter can run in server environments, it does not support hydration data parsing like createBrowserRouter. It initializes with a fresh memory history based on initialEntries rather than restoring server-rendered loader data. For SSR applications requiring state hydration from the server, createBrowserRouter with parseHydrationData() is the correct choice.

Can I use createBrowserRouter in unit tests?

Technically yes, but it requires a DOM environment (like JSDOM) and will interact with the browser's History API, potentially causing side effects between tests. For isolated, deterministic tests, createMemoryRouter is strongly recommended because it avoids URL bar manipulation and allows precise control over the initial route history through initialEntries and initialIndex.

What are the performance implications of each router?

Both routers share the same underlying createRouter core, so routing logic performance is identical. The difference lies in history management overhead: createBrowserRouter incurs the cost of native History API calls and URL bar updates, while createMemoryRouter performs simple array operations. In practice, this difference is negligible for application performance but significant for test isolation and environment compatibility.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →