Deploy to Production

Purpose and Scope

Production deployment for a TanStack Router application is mostly about making the host respect the same route tree that the browser uses at runtime. In a single page application, URLs such as /settings/profile or /invoices/123 are not separate static files. They are client-side routes that should load the built index.html, after which Router matches the location, runs route-level behavior, and renders the correct nested UI. This page explains the deployment decisions that keep those URLs refresh-safe, shareable, and compatible with typed Router APIs.

Sources: docs/router/how-to/deploy-to-production.md, docs/router/api/router.md

The repository documentation separates client-only Router deployment from TanStack Start deployment. Router SPAs need fallback rewrites to index.html; Start applications add server rendering and server-function concerns, so the host must also route appropriate requests to server output. Treat these as different deployment modes even if they share the same route tree and authoring model. A Router-only app can usually deploy to static hosting, while a Start SSR app needs the platform-specific server or functions integration shown in the deployment guide.

Sources: docs/router/how-to/deploy-to-production.md

Relevant Source Files

  • docs/router/how-to/deploy-to-production.md — primary how-to for production hosting, SPA fallbacks, Netlify, Cloudflare Pages, Vercel, GitHub Pages, and Start SSR variants.
  • docs/router/devtools.md — documents Router devtools packages, root-route usage, manual router injection, floating mode, and the production-only TanStackRouterDevtoolsInProd imports.
  • docs/router/api/file-based-routing.md — defines file-based routing configuration such as routesDirectory, generatedRouteTree, autoCodeSplitting, ignore options, and route tree output behavior.
  • docs/router/api/router.md — maps the public Router API surface used after deployment, including route creation, navigation, redirects, links, loaders, hooks, components, and core types.
  • docs/router/api/router/ActiveLinkOptionsType.md — defines active and inactive link props, useful when validating production navigation styling after rewrites are configured.
  • docs/router/api/router/AsyncRouteComponentType.md — defines code-split route components with an optional preload() method, relevant to production bundle loading and preloading checks.

Deployment Model

Start by deciding whether the build artifact is a static SPA or a server-rendered application. For a static SPA, the key invariant is that every application route should return the same HTML entry point with a successful status. The browser then downloads the JavaScript bundle, Router parses the current location, and the route tree determines what to render. Without that fallback, direct visits or reloads on deep links often produce host-level 404s before Router can run.

Sources: docs/router/how-to/deploy-to-production.md

File-based routing adds a build-time contract to that deployment model. The file-based routing reference defines routesDirectory as the source directory for route files and generatedRouteTree as the output file where the route tree is saved. By default those are ./src/routes and ./src/routeTree.gen.ts. Production builds should include an up-to-date generated route tree so links, params, search schemas, loader data, and route matching remain aligned with the files that define the application.

Sources: docs/router/api/file-based-routing.md

The Router API reference shows why this generated contract matters after deployment: applications use public functions and components such as createRouter, createFileRoute, <Link>, <Navigate>, <Outlet>, redirect, and route hooks including useNavigate, useLocation, useParams, useSearch, and useLoaderData. These APIs assume the route tree is known. Hosting rewrites do not replace Router matching; they merely ensure the request reaches the client entry so those APIs can operate on the parsed location.

Sources: docs/router/api/router.md

Platform Rewrite Recipes

For Netlify, the documented SPA solution is a catch-all redirect to /index.html with status 200. You can place this in public/_redirects or in the build output. The same behavior can be expressed in netlify.toml, where the build section also identifies dist as the publish directory and npm run build as the build command. The important detail is the 200 rewrite: it preserves client-side routing instead of issuing a user-visible redirect or returning a platform 404.

Sources: docs/router/how-to/deploy-to-production.md

/*    /index.html   200
[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200
 
[build]
  publish = "dist"
  command = "npm run build"

Cloudflare Pages follows the same SPA fallback principle. The guide shows public/_redirects with a catch-all rewrite to /index.html, and also shows public/_routes.json when you need more control over included and excluded paths. That distinction matters when an application has API endpoints or server behavior outside Router’s client-side route tree. The example excludes /api/*, which prevents static fallback routing from swallowing requests that should be handled by another runtime.

Sources: docs/router/how-to/deploy-to-production.md

{
  "version": 1,
  "include": ["/*"],
  "exclude": ["/api/*"]
}

For Vercel, the repository guide uses vercel.json rewrites from /(.*) to /index.html for a client-only SPA. It also calls out that Start SSR applications use a different configuration, with a server runtime and routes that forward requests to the server entry. Do not mix these mental models accidentally: a static rewrite is correct when the browser owns rendering, while an SSR setup must let the server participate before hydration.

Sources: docs/router/how-to/deploy-to-production.md

{
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "/index.html"
    }
  ]
}

GitHub Pages has a different constraint because it does not support arbitrary server rewrites. The documented workaround is to copy the built dist/index.html to dist/404.html after the build, allowing deep links to fall back to the application shell. When deploying a project under a repository subpath, the Vite configuration should set base to the repository name and continue using the TanStack Router plugin before the framework plugin, including options such as target: 'react' and autoCodeSplitting: true. Sources: docs/router/how-to/deploy-to-production.md, docs/router/api/file-based-routing.md

cp dist/index.html dist/404.html

Build-Time Route Generation and Bundles

File-based route generation is part of production readiness, not just local developer experience. The file-based routing API exposes routeFilePrefix, routeFileIgnorePrefix, routeFileIgnorePattern, indexToken, routeToken, disableTypes, addExtensions, routeTreeFileHeader, routeTreeFileFooter, enableRouteTreeFormatting, and tmpDir. These options shape which files become routes and where the generated tree is written. Before deploying, verify that ignored co-located files are not accidentally included and that renamed route conventions still produce the URLs your host rewrites are intended to support.

Sources: docs/router/api/file-based-routing.md

Code splitting should be verified as part of the same production pass. The file-based routing reference includes autoCodeSplitting, and the AsyncRouteComponent type describes a code-split route component as a synchronous route component with an optional preload() method. That means production navigation can involve both route matching and asynchronous chunk loading. After building, test direct visits and in-app transitions for routes that are split into separate chunks, especially when the app is deployed under a non-root base path.

Sources: docs/router/api/file-based-routing.md, docs/router/api/router/AsyncRouteComponentType.md

Active navigation styles are another practical production signal. The ActiveLinkOptions type extends link options with activeProps and inactiveProps, each either anchor attributes or a function returning anchor attributes. Once rewrites are configured, refresh a deep URL and confirm that active links still receive the expected styling. This validates both the host fallback and Router’s route matching from the initial location, not only client-side transitions that started from the home page.

Sources: docs/router/api/router/ActiveLinkOptionsType.md

Devtools and Production Diagnostics

Router devtools are intended to help during development by visualizing Router internals. The Router devtools page documents installation through framework-specific packages such as @tanstack/react-router-devtools and @tanstack/solid-router-devtools, and shows rendering them in the root route near <Outlet /> so they automatically connect to the router instance. It also supports manually passing the router instance when the devtools are rendered outside the provider. These patterns are useful while validating routing behavior before release.

Sources: docs/router/devtools.md

In production, the normal TanStackRouterDevtools import is documented as not being shown. If you intentionally want devtools in an environment where process.env.NODE_ENV === 'production', the repository docs provide TanStackRouterDevtoolsInProd imports for React and Solid with the same options. Use this deliberately and sparingly. A safer release workflow is usually to validate with devtools in staging, keep production bundles clean, and rely on platform logs or application observability once the rewrite and route generation checks pass.

Sources: docs/router/devtools.md

import { TanStackRouterDevtoolsInProd } from '@tanstack/react-router-devtools'

Compact Production Checklist

  • Choose the deployment mode: static Router SPA or TanStack Start SSR.
  • For a static SPA, configure a catch-all rewrite or fallback to index.html.
  • For API or SSR paths, exclude or forward those paths instead of sending every request to the static fallback.
  • Ensure the file-based route tree is generated from the intended routesDirectory into generatedRouteTree.
  • If deploying under a subpath, configure the bundler base path and test deep links.
  • Build and test direct visits, refreshes, link transitions, active link styling, loader-backed routes, and code-split route chunks.
  • Keep normal Router devtools out of production unless you intentionally import the production devtools variant.

Sources: docs/router/how-to/deploy-to-production.md, docs/router/devtools.md, docs/router/api/file-based-routing.md, docs/router/api/router/ActiveLinkOptionsType.md, docs/router/api/router/AsyncRouteComponentType.md

Next Steps

After applying the host-specific configuration, run the production build locally or in a preview environment and test URLs the same way users will open them: pasted into the address bar, refreshed from a deep route, reached through <Link>, and reached through imperative navigation. If a URL works through navigation but fails on refresh, the problem is almost always the host fallback. If refresh works but a route renders the wrong UI or has missing types, inspect the generated route tree and file-based routing configuration next.

Sources: docs/router/how-to/deploy-to-production.md, docs/router/api/file-based-routing.md, docs/router/api/router.md