UNPKG

@tanstack/start-client-core

Version:

Modern and scalable routing for React applications

307 lines (239 loc) 7.26 kB
--- name: start-core/deployment description: >- Deploy to Cloudflare Workers, Netlify, Vercel, Node.js/Docker, Bun, Railway. Selective SSR (ssr option per route), SPA mode, static prerendering, ISR with Cache-Control headers, SEO and head management. type: sub-skill library: tanstack-start library_version: '1.166.2' requires: - start-core sources: - TanStack/router:docs/start/framework/react/guide/hosting.md - TanStack/router:docs/start/framework/react/guide/selective-ssr.md - TanStack/router:docs/start/framework/react/guide/static-prerendering.md - TanStack/router:docs/start/framework/react/guide/seo.md --- # Deployment and Rendering TanStack Start deploys to any hosting provider via Vite and Nitro. This skill covers hosting setup, SSR configuration, prerendering, and SEO. ## Hosting Providers ### Cloudflare Workers ```bash pnpm add -D @cloudflare/vite-plugin wrangler ``` ```ts // vite.config.ts import { defineConfig } from 'vite' import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { cloudflare } from '@cloudflare/vite-plugin' import viteReact from '@vitejs/plugin-react' export default defineConfig({ plugins: [ cloudflare({ viteEnvironment: { name: 'ssr' } }), tanstackStart(), viteReact(), ], }) ``` ```jsonc // wrangler.jsonc { "name": "my-app", "compatibility_date": "2025-09-02", "compatibility_flags": ["nodejs_compat"], "main": "@tanstack/react-start/server-entry", } ``` Deploy: `npx wrangler login && pnpm run deploy` ### Netlify ```bash pnpm add -D @netlify/vite-plugin-tanstack-start ``` ```ts // vite.config.ts import { defineConfig } from 'vite' import { tanstackStart } from '@tanstack/react-start/plugin/vite' import netlify from '@netlify/vite-plugin-tanstack-start' import viteReact from '@vitejs/plugin-react' export default defineConfig({ plugins: [tanstackStart(), netlify(), viteReact()], }) ``` Deploy: `npx netlify deploy` ### Nitro (Vercel, Railway, Node.js, Docker) ```bash npm install nitro@npm:nitro-nightly@latest ``` ```ts // vite.config.ts import { defineConfig } from 'vite' import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { nitro } from 'nitro/vite' import viteReact from '@vitejs/plugin-react' export default defineConfig({ plugins: [tanstackStart(), nitro(), viteReact()], }) ``` Build and start: `npm run build && node .output/server/index.mjs` ### Bun Bun deployment requires React 19. For React 18, use Node.js deployment. ```ts // vite.config.ts — add bun preset to nitro plugins: [tanstackStart(), nitro({ preset: 'bun' }), viteReact()] ``` ## Selective SSR Control SSR per route with the `ssr` property. ### `ssr: true` (default) Runs `beforeLoad` and `loader` on server, renders component on server: ```tsx export const Route = createFileRoute('/posts/$postId')({ ssr: true, // default loader: () => fetchPost(), // runs on server during SSR component: PostPage, // rendered on server }) ``` ### `ssr: false` Disables server execution of `beforeLoad`/`loader` and server rendering: ```tsx export const Route = createFileRoute('/dashboard')({ ssr: false, loader: () => fetchDashboard(), // runs on client only component: DashboardPage, // rendered on client only }) ``` ### `ssr: 'data-only'` Runs `beforeLoad`/`loader` on server but renders component on client only: ```tsx export const Route = createFileRoute('/canvas')({ ssr: 'data-only', loader: () => fetchCanvasData(), // runs on server component: CanvasPage, // rendered on client only }) ``` ### Functional Form Decide SSR at runtime based on params/search: ```tsx export const Route = createFileRoute('/docs/$docType/$docId')({ ssr: ({ params }) => { if (params.status === 'success' && params.value.docType === 'sheet') { return false } }, }) ``` ### SSR Inheritance Children inherit parent SSR config and can only be MORE restrictive: - `true` → `data-only` or `false` (allowed) - `false` → `true` (NOT allowed — parent `false` wins) ### Default SSR Change the default for all routes in `src/start.ts`: ```tsx import { createStart } from '@tanstack/react-start' export const startInstance = createStart(() => ({ defaultSsr: false, })) ``` ## Static Prerendering Generate static HTML at build time: ```ts // vite.config.ts tanstackStart({ prerender: { enabled: true, crawlLinks: true, concurrency: 14, failOnError: true, }, }) ``` Static routes are auto-discovered. Dynamic routes (e.g. `/users/$userId`) require `crawlLinks` or explicit `pages` config. ## SEO and Head Management ### Basic Meta Tags ```tsx export const Route = createFileRoute('/')({ head: () => ({ meta: [ { title: 'My App - Home' }, { name: 'description', content: 'Welcome to My App' }, ], }), }) ``` ### Dynamic Meta from Loader Data ```tsx export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params }) => fetchPost(params.postId), head: ({ loaderData }) => ({ meta: [ { title: loaderData.title }, { name: 'description', content: loaderData.excerpt }, { property: 'og:title', content: loaderData.title }, { property: 'og:image', content: loaderData.coverImage }, ], }), }) ``` ### Structured Data (JSON-LD) ```tsx head: ({ loaderData }) => ({ scripts: [ { type: 'application/ld+json', children: JSON.stringify({ '@context': 'https://schema.org', '@type': 'Article', headline: loaderData.title, }), }, ], }) ``` ### Dynamic Sitemap via Server Route ```ts // src/routes/sitemap[.]xml.ts export const Route = createFileRoute('/sitemap.xml')({ server: { handlers: { GET: async () => { const posts = await fetchAllPosts() const sitemap = `<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> ${posts.map((p) => `<url><loc>https://myapp.com/posts/${p.id}</loc></url>`).join('')} </urlset>` return new Response(sitemap, { headers: { 'Content-Type': 'application/xml' }, }) }, }, }, }) ``` ## Common Mistakes ### 1. HIGH: Missing nodejs_compat flag for Cloudflare Workers ```jsonc // WRONG — Node.js APIs fail at runtime { "compatibility_flags": [] } // CORRECT { "compatibility_flags": ["nodejs_compat"] } ``` ### 2. MEDIUM: Bun deployment with React 18 Bun-specific deployment only works with React 19. Use Node.js deployment for React 18. ### 3. MEDIUM: Child route loosening parent SSR config ```tsx // Parent sets ssr: false // WRONG — child cannot upgrade to ssr: true const parentRoute = createFileRoute('/dashboard')({ ssr: false }) const childRoute = createFileRoute('/dashboard/stats')({ ssr: true, // IGNORED — parent false wins }) // CORRECT — children can only be MORE restrictive const parentRoute = createFileRoute('/dashboard')({ ssr: 'data-only' }) const childRoute = createFileRoute('/dashboard/stats')({ ssr: false, // OK — more restrictive than parent }) ``` ## Cross-References - [start-core/server-routes](../server-routes/SKILL.md) — API endpoints for sitemaps, robots.txt - [start-core/execution-model](../execution-model/SKILL.md) — SSR affects where code runs