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 locationsfuture: Future flags for v7 compatibilitywindow: Custom window object injection for SSR environmentshydrationData: Server-rendered state restored viaparseHydrationData()
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:
createBrowserRouteruses the browser's History API (createBrowserHistory) for URL synchronization, whilecreateMemoryRouteruses an in-memory array (createMemoryHistory) with no URL changes. -
Environment: Use
createBrowserRouterfor production browser apps and SSR hydration; usecreateMemoryRouterfor testing, React Native, and non-DOM environments. -
Configuration:
createBrowserRouteracceptsbasename,window, andhydrationDatafor SSR;createMemoryRouteracceptsinitialEntriesandinitialIndexfor deterministic state setup. -
Implementation: Both instantiate the same
createRoutercore but differ in history implementation—packages/react-router/lib/dom/lib.tsxfor browser,packages/react-router/lib/components.tsxfor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →