Skip to main content

createHashRouter

Create a new data router that manages the application path via the URL hash. This is useful for web applications that cannot configure the server to direct all traffic to the React Router application.

Function Signature

function createHashRouter(
  routes: RouteObject[],
  opts?: DOMRouterOpts
): DataRouter

Route Configuration

The routes parameter accepts the same RouteObject[] configuration as createBrowserRouter. See the createBrowserRouter route configuration for complete details.
interface RouteObject {
  path?: string;
  caseSensitive?: boolean;
  index?: boolean;
  element?: React.ReactNode;
  Component?: React.ComponentType;
  loader?: LoaderFunction;
  action?: ActionFunction;
  errorElement?: React.ReactNode;
  ErrorBoundary?: React.ComponentType;
  children?: RouteObject[];
  // ... and more
}

Options Parameter

interface DOMRouterOpts {
  // Base path for all routes
  basename?: string;
  
  // Context provider function for loaders/actions
  getContext?: () => RouterContextProvider;
  
  // Future flags for opt-in features
  future?: Partial<FutureConfig>;
  
  // Initial data from server-side rendering
  hydrationData?: HydrationState;
  
  // Instrumentation for observability
  unstable_instrumentations?: unstable_ClientInstrumentation[];
  
  // Custom data loading strategy
  dataStrategy?: DataStrategyFunction;
  
  // Lazy route discovery
  patchRoutesOnNavigation?: PatchRoutesOnNavigationFunction;
  
  // Window object override (defaults to global window)
  window?: Window;
}

Return Type

Returns an initialized DataRouter instance to be passed to <RouterProvider>.

Examples

Basic Usage

import { createHashRouter, RouterProvider } from "react-router";
import { createRoot } from "react-dom/client";

const router = createHashRouter([
  {
    path: "/",
    element: <Root />,
    children: [
      {
        index: true,
        element: <Home />,
      },
      {
        path: "about",
        element: <About />,
      },
      {
        path: "contact",
        element: <Contact />,
        action: contactAction,
      },
    ],
  },
]);

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

// URLs will be:
// http://example.com/#/
// http://example.com/#/about
// http://example.com/#/contact

With Loaders and Actions

async function rootLoader() {
  const user = await fetchUser();
  return { user };
}

async function productsLoader() {
  const products = await fetchProducts();
  return { products };
}

async function productAction({ request, params }) {
  const formData = await request.formData();
  await updateProduct(params.id, formData);
  return { success: true };
}

const router = createHashRouter([
  {
    path: "/",
    element: <Root />,
    loader: rootLoader,
    children: [
      {
        path: "products",
        element: <Products />,
        loader: productsLoader,
      },
      {
        path: "products/:id",
        element: <ProductDetail />,
        action: productAction,
      },
    ],
  },
]);

With Error Boundaries

const router = createHashRouter([
  {
    path: "/",
    element: <Root />,
    errorElement: <RootErrorBoundary />,
    children: [
      {
        path: "dashboard",
        element: <Dashboard />,
        errorElement: <DashboardError />,
        loader: async () => {
          const data = await fetchDashboard();
          if (!data) {
            throw new Response("Not Found", { status: 404 });
          }
          return data;
        },
      },
    ],
  },
]);

function RootErrorBoundary() {
  const error = useRouteError();
  
  if (isRouteErrorResponse(error)) {
    return (
      <div>
        <h1>{error.status} {error.statusText}</h1>
        <p>{error.data}</p>
      </div>
    );
  }
  
  return (
    <div>
      <h1>Oops!</h1>
      <p>{error.message}</p>
    </div>
  );
}

With Basename

const router = createHashRouter(
  [
    {
      path: "/",
      element: <Home />,
    },
    {
      path: "/about",
      element: <About />,
    },
  ],
  {
    basename: "/app",
  }
);

// URLs will be:
// http://example.com/#/app/
// http://example.com/#/app/about

With Lazy Loading

const router = createHashRouter([
  {
    path: "/",
    element: <Root />,
    children: [
      {
        index: true,
        element: <Home />,
      },
      {
        path: "dashboard",
        lazy: async () => {
          const { Dashboard, loader } = await import("./dashboard");
          return {
            Component: Dashboard,
            loader,
          };
        },
      },
      {
        path: "settings",
        lazy: () => import("./settings"),
      },
    ],
  },
]);

Static File Hosting Example

// Perfect for GitHub Pages, Netlify, Vercel, etc.
const router = createHashRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      {
        index: true,
        element: <HomePage />,
      },
      {
        path: "docs",
        element: <Docs />,
        loader: docsLoader,
      },
      {
        path: "blog",
        children: [
          {
            index: true,
            element: <BlogList />,
            loader: blogListLoader,
          },
          {
            path: ":slug",
            element: <BlogPost />,
            loader: blogPostLoader,
          },
        ],
      },
    ],
  },
]);

// GitHub Pages deployment:
// https://username.github.io/repo-name/#/
// https://username.github.io/repo-name/#/docs
// https://username.github.io/repo-name/#/blog

With Hydration Data

const router = createHashRouter(
  [
    {
      id: "root",
      path: "/",
      Component: Root,
      children: [
        {
          id: "home",
          index: true,
          Component: Home,
          loader: homeLoader,
        },
      ],
    },
  ],
  {
    hydrationData: window.__HYDRATION_DATA__,
  }
);

Use Cases

When to Use createHashRouter

Use createHashRouter when:
  • Static file hosting: Deploying to services like GitHub Pages, AWS S3, or other static hosts
  • No server configuration: Cannot configure server to handle client-side routes
  • Legacy constraints: Working with legacy systems or CDNs that don’t support rewrite rules
  • File protocol: App runs via file:// protocol (e.g., Electron without custom protocol)
  • Fallback option: Server configuration is complex or impossible
  • Simple deployment: Want to avoid server configuration entirely

Advantages

  • No server configuration needed: Works on any static file host
  • All data router features: Full support for loaders, actions, lazy loading, etc.
  • Simple deployment: Just upload files and go
  • Legacy compatibility: Works in older hosting environments
  • Works everywhere: Functions on any web server, even without special configuration

Disadvantages

  • URLs include hash: Less clean URLs (e.g., example.com/#/about vs example.com/about)
  • SEO impact: Hash portion not sent to server, may affect SEO
  • Analytics: May require special configuration for page tracking
  • Server-side rendering limitations: Hash routing doesn’t work well with SSR
  • Sharing: URLs with hashes are less intuitive for users

How Hash Routing Works

The hash router uses the URL hash fragment to store the route:
https://example.com/#/products/123
                    └─────────────┘
                    Hash fragment
                    (not sent to server)
Key characteristics:
  1. Client-side only: Hash portion (#/products/123) never sent to server
  2. No server changes: Server always serves index.html regardless of URL
  3. Browser history: Still uses browser back/forward buttons
  4. Anchor behavior: Overrides default anchor link behavior within app

Deployment Examples

GitHub Pages

// package.json
{
  "homepage": "https://username.github.io/repo-name",
  "scripts": {
    "predeploy": "npm run build",
    "deploy": "gh-pages -d build"
  }
}
// Router setup
const router = createHashRouter([
  {
    path: "/",
    element: <App />,
  },
]);

AWS S3 Static Website

// No special S3 configuration needed!
const router = createHashRouter([
  {
    path: "/",
    element: <Root />,
    children: [
      { index: true, element: <Home /> },
      { path: "about", element: <About /> },
    ],
  },
]);

Netlify/Vercel

While these platforms support createBrowserRouter with redirects, createHashRouter works without any configuration:
const router = createHashRouter(routes);
// No _redirects or vercel.json needed

SEO Considerations

Hash routing has SEO limitations:
# Search engines see:
https://example.com/

# Not:
https://example.com/#/about
https://example.com/#/products/123
Solutions:
  1. Use createBrowserRouter instead: Best for SEO
  2. Server-side rendering: Pre-render pages for crawlers
  3. Dynamic rendering: Detect bots and serve static HTML
  4. Sitemap submission: Help search engines discover pages

Migration from createBrowserRouter

Switching between routers is simple:
// Before
import { createBrowserRouter } from "react-router";
const router = createBrowserRouter(routes);

// After
import { createHashRouter } from "react-router";
const router = createHashRouter(routes);
// Same routes array, same everything else!
Users’ bookmarks will break, so consider:
  • Adding redirects from old URLs
  • Communicating the change
  • Updating external links

Differences from Other Routers

FeaturecreateHashRoutercreateBrowserRoutercreateMemoryRouter
URL Format#/path/pathN/A
Server Config✗ Not needed✓ Required✗ Not needed
SEO Friendly✗ No✓ YesN/A
Clean URLs✗ No✓ YesN/A
Static Hosting✓ Perfect✗ Needs configN/A
SSR SupportLimited✓ Full✓ Full
Browser History✓ Yes✓ Yes✗ In-memory
Production Use✓ Fallback✓ Recommended✗ Testing only

Analytics Configuration

Hash changes may require special tracking:

Google Analytics 4

import { useEffect } from "react";
import { useLocation } from "react-router";

function GoogleAnalytics() {
  const location = useLocation();
  
  useEffect(() => {
    // Track hash route changes
    gtag("event", "page_view", {
      page_path: location.pathname + location.search + location.hash,
    });
  }, [location]);
  
  return null;
}

// Add to your app
function App() {
  return (
    <RouterProvider router={router}>
      <GoogleAnalytics />
      {/* rest of app */}
    </RouterProvider>
  );
}

Other Analytics

Most analytics libraries need similar configuration to track hash changes as page views.

Build docs developers (and LLMs) love