SSR and Advanced UX Examples

Purpose and Scope

This page orients you to the example shape used for server-rendering and advanced user-experience work in the TanStack Router repository. The targeted source paths for this page are intentionally small: they show CLI-generated Router-only applications across React, Solid, and Vue, plus the installation guide for using the Router CLI when a supported bundler integration is not available. That makes them useful as a baseline before layering on SSR, streaming, navigation blocking, route masking, scroll restoration, or view transitions. The examples answer the practical question: what does a generated route tree look like once it is mounted into a framework app?

The key distinction is between a Router-only SPA and a TanStack Start full-stack application. Official CLI examples describe tanstack create my-app -y as the default Start app with SSR, while tanstack create my-app --router-only -y creates a Router-only SPA. The repository paths used here reflect the Router-only side of that split: they mount a generated routeTree, render a RouterProvider, and configure client navigation behavior. Treat them as the smallest runnable harness for advanced UX options before moving to Start examples for full-document SSR and streaming.

Sources: docs/router/installation/with-router-cli.md, e2e/react-router/generator-cli-only/src/main.tsx, e2e/solid-router/generator-cli-only/src/main.tsx, e2e/vue-router/generator-cli-only/src/main.tsx

Relevant Source Files

  • docs/router/installation/with-router-cli.md explains when to use the TanStack Router CLI, the tsr generate and tsr watch commands, package scripts, generated route tree defaults, and guidance for ignoring routeTree.gen.ts.
  • e2e/react-router/generator-cli-only/src/main.tsx is the React CLI-only app entry point. It creates the router from routeTree, enables intent preloading, sets a stale time, turns on scroll restoration, registers router types, and renders RouterProvider.
  • e2e/react-router/generator-cli-only/src/routes/index.tsx is the generated-file-routing home route for the React example, using createFileRoute('/') and a Home component.
  • e2e/solid-router/generator-cli-only/src/main.tsx mirrors the React entry point for Solid, using @tanstack/solid-router, solid-js/web rendering, the same router options, and module registration for type safety.
  • e2e/solid-router/generator-cli-only/src/routes/index.tsx is the Solid file route for /, showing the same route contract with Solid JSX conventions.
  • e2e/vue-router/generator-cli-only/src/main.tsx mirrors the entry-point pattern for Vue, using @tanstack/vue-router, createApp, RouterProvider, and the same generated route tree configuration.

Baseline Example Flow

The Router CLI guide describes a workflow for projects that are not using a supported bundler plugin. In that workflow, file-based routes live under a routes directory, and the CLI produces a generated route tree file that application code imports. The guide recommends scripts such as generate-routes for one-time generation and watch-routes for development, and it warns that the CLI is only responsible for route tree generation. It does not provide the other bundler-integrated features that the Router plugin can provide.

{
  "scripts": {
    "generate-routes": "tsr generate",
    "watch-routes": "tsr watch",
    "build": "npm run generate-routes && ...",
    "dev": "npm run watch-routes && ..."
  }
}

Once the route tree exists, each framework example follows the same runtime pattern. The app imports routeTree from ./routeTree.gen, calls createRouter({ routeTree, ...options }), registers the router type through the framework package's Register interface, and renders a RouterProvider. This separation is important for advanced examples: route files define route behavior, generated code stitches the route tree together, and the entry point decides global router behavior such as preloading, cache freshness, and scroll restoration.

Sources: docs/router/installation/with-router-cli.md, e2e/react-router/generator-cli-only/src/main.tsx, e2e/solid-router/generator-cli-only/src/main.tsx, e2e/vue-router/generator-cli-only/src/main.tsx

Advanced UX Options Demonstrated

The most concrete advanced UX option shown in these examples is scrollRestoration: true. Scroll restoration tells the router to participate in preserving and restoring scroll positions across navigation. It belongs at router creation time because it is a global navigation concern rather than a single route component concern. If you are validating scroll behavior in an example app, this option is the switch to look for before inspecting individual routes, nested layouts, or browser history interactions.

The examples also set defaultPreload: 'intent' and defaultStaleTime: 5000. Intent preloading is a navigation UX optimization: it lets the router begin preparing the next route when the user shows intent, such as interacting with a link, rather than waiting until navigation is committed. The stale-time option gives preloaded or loaded data a freshness window. Together, these settings make a tiny generated app behave more like a production navigation surface, where responsiveness and avoiding unnecessary reload work both matter.

const router = createRouter({
  routeTree,
  defaultPreload: 'intent',
  defaultStaleTime: 5000,
  scrollRestoration: true,
})

Navigation blocking, route masking, and view transitions are best read as features to add on top of this same entry-point structure. They are not shown directly in the targeted snippets, but they use the same router instance and generated route tree foundation. In practice, you start from a working route tree, confirm that RouterProvider is mounted for the framework, then add route-level or component-level APIs for blocking unsaved changes, displaying modal routes under alternate URLs, or coordinating page transition animations.

Sources: e2e/react-router/generator-cli-only/src/main.tsx, e2e/solid-router/generator-cli-only/src/main.tsx, e2e/vue-router/generator-cli-only/src/main.tsx

Framework-by-Framework Mapping

The React example uses ReactDOM.createRoot and wraps RouterProvider in React.StrictMode. It also guards mounting with if (!rootElement.innerHTML), which keeps the client entry from blindly replacing existing markup. That guard is useful to notice when comparing SPA examples to SSR-capable setups: the client entry is written so it can detect whether the root element already contains HTML, even though the targeted example itself is a CLI-only Router app.

The Solid example keeps the same router options but swaps in Solid rendering primitives. It imports from @tanstack/solid-router, uses render(() => <RouterProvider router={router} />, rootElement), and registers the router with the Solid package's Register interface. The file route uses createFileRoute('/') and a Solid component returning JSX with class rather than React's className. This demonstrates that the shared Router concepts survive framework-specific rendering differences.

The Vue example follows the same generated route tree contract while adapting the mount layer to Vue. It creates a Vue app whose setup function returns a render function for RouterProvider, then mounts to #app. The important design signal is that createRouter, routeTree, RouterProvider, defaultPreload, defaultStaleTime, scrollRestoration, and Register all remain recognizable across frameworks. Advanced UX examples should therefore be compared by router behavior first and framework syntax second.

Sources: e2e/react-router/generator-cli-only/src/main.tsx, e2e/react-router/generator-cli-only/src/routes/index.tsx, e2e/solid-router/generator-cli-only/src/main.tsx, e2e/solid-router/generator-cli-only/src/routes/index.tsx, e2e/vue-router/generator-cli-only/src/main.tsx

SSR and Streaming Path

For SSR and streaming, start from the CLI distinction rather than from these SPA entry points. The official CLI examples identify TanStack Start creation as the default full-stack path with SSR, and Router-only creation as the no-SSR path. That means these generator-cli-only examples are not the final shape for a Start app; they are the lowest-level Router harness. Use them to understand how routes and provider mounting work, then move to Start-specific examples when you need full-document SSR, streaming responses, server functions, and production server entry points.

This separation helps avoid a common debugging mistake. If an advanced UX issue appears in a Start app, first decide whether the problem belongs to core Router behavior or to the server-rendering layer. Route matching, generated route trees, link preloading, stale data freshness, scroll restoration, and typed route registration can be reproduced in a Router-only example like these. HTML streaming, server-only execution, hydration timing, and deployment behavior belong in Start examples and should be debugged with the full-stack entry points enabled.

Testing Signals and Next Steps

These files live under e2e/.../generator-cli-only, which signals that the repository uses them as executable coverage for the CLI-only route-generation path. The route files are deliberately simple, but the app entries exercise the real framework packages and the generated routeTree.gen import shape. When you build your own reproduction, keep it similarly small: one generated home route, one framework entry point, one router instance, and the specific advanced UX option you want to validate.

Next, read the dedicated pages for the feature you are layering in. Use server-side-rendering and start-overview for SSR and streaming, navigation-blocking for unsaved-change flows, route-masking-and-url-rewrites for modal or alternate URL patterns, and scroll-restoration-and-view-transitions for scroll and animated navigation behavior. Return to this page when you need a clean baseline that proves route generation, type registration, provider mounting, intent preloading, stale-time configuration, and scroll restoration are wired correctly before adding more moving parts.