TanStack Start static rendering example
Explore SPA mode, prerendered routes, and static server-function results in a React application built with TanStack Start.
This example combines SPA mode with link crawling during prerendering. Its post functions use static-function middleware to capture results for static output. Use it to understand the build configuration before adapting it to your own public content.
Run locally
Use Node.js 22.12 or newer and pnpm 11. These examples fetch public sample data from JSONPlaceholder, so an internet connection is required. No API key or database is needed.
git clone https://github.com/TanStack/router.git
cd router
pnpm install
cd examples/react/start-basic-static
pnpm devVisit http://localhost:3000/test/ in development. Before publishing, replace the sample sitemap host and base path. The example sets failOnError to false, so inspect build output for failed pages. Static output is a build-time snapshot, not a live database.
Run pnpm build from the example directory to build the app and check its types.
Files to follow
- vite.config.ts: Configures the /test/ base path, SPA prerendering, link crawling, and sitemap host.
- src/utils/posts.tsx: Uses static-function middleware for public sample posts.
- src/routes/posts.$postId.tsx: Loads and displays an individual post.
/// <reference types="vite/client" />
import {
HeadContent,
Link,
Outlet,
Scripts,
createRootRoute,
} from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
import * as React from 'react'
import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary'
import { NotFound } from '~/components/NotFound'
import appCss from '~/styles/app.css?url'
import { seo } from '~/utils/seo'
export const Route = createRootRoute({
head: () => ({
meta: [
{
charSet: 'utf-8',
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1',
},
...seo({
title:
'TanStack Start | Type-Safe, Client-First, Full-Stack React Framework',
description: `TanStack Start is a type-safe, client-first, full-stack React framework. `,
}),
],
links: [
{ rel: 'stylesheet', href: appCss },
{
rel: 'apple-touch-icon',
sizes: '180x180',
href: '/apple-touch-icon.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '32x32',
href: '/favicon-32x32.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '16x16',
href: '/favicon-16x16.png',
},
{ rel: 'manifest', href: '/site.webmanifest', color: '#fffff' },
{ rel: 'icon', href: '/favicon.ico' },
],
}),
errorComponent: (props) => {
return (
<RootLayout>
<DefaultCatchBoundary {...props} />
</RootLayout>
)
},
notFoundComponent: () => <NotFound />,
component: RootComponent,
})
function RootComponent() {
return (
<RootLayout>
<Outlet />
</RootLayout>
)
}
function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<head>
<HeadContent />
</head>
<body>
<div className="p-2 flex gap-2 text-lg">
<Link
to="/"
activeProps={{
className: 'font-bold',
}}
activeOptions={{ exact: true }}
>
Home
</Link>{' '}
<Link
to="/posts"
activeProps={{
className: 'font-bold',
}}
>
Posts
</Link>{' '}
<Link
to="/users"
activeProps={{
className: 'font-bold',
}}
>
Users
</Link>{' '}
<Link
to="/route-a"
activeProps={{
className: 'font-bold',
}}
>
Pathless Layout
</Link>{' '}
<Link
to="/deferred"
activeProps={{
className: 'font-bold',
}}
>
Deferred
</Link>{' '}
<Link
// @ts-expect-error
to="/this-route-does-not-exist"
activeProps={{
className: 'font-bold',
}}
>
This Route Does Not Exist
</Link>
</div>
<hr />
{children}
<TanStackRouterDevtools position="bottom-right" />
<Scripts />
</body>
</html>
)
}