deployment

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Deployment and Rendering

部署与渲染

TanStack Start deploys to any hosting provider via Vite and Nitro. This skill covers hosting setup, SSR configuration, prerendering, and SEO.
TanStack Start 可通过 Vite 和 Nitro 部署到任意托管服务商。本技能涵盖托管配置、SSR 设置、预渲染和 SEO 相关内容。

Hosting Providers

托管服务商

Cloudflare Workers

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
Worker env is per-request. Cloudflare Workers inject env vars at request time.
process.env.X
at module scope evaluates to
undefined
even on the server. The Cloudflare-canonical way to read env (including from module scope) is the
cloudflare:workers
env binding:
ts
import { env } from 'cloudflare:workers'
const apiHost = env.API_HOST
Or read
process.env.X
per-request inside
.handler()
/ middleware
.server()
. See Cloudflare's environment-variables docs and start-core/execution-model.
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",
}
部署命令:
npx wrangler login && pnpm run deploy
Worker 的环境变量是按请求注入的。 Cloudflare Workers 会在请求时注入环境变量。模块作用域下的
process.env.X
即使在服务端也会返回
undefined
。Cloudflare 官方推荐的环境变量读取方式(包括模块作用域)是使用
cloudflare:workers
环境绑定:
ts
import { env } from 'cloudflare:workers'
const apiHost = env.API_HOST
或者在
.handler()
/ 中间件
.server()
中按请求读取
process.env.X
。参见 Cloudflare 环境变量文档 和 start-core/execution-model。

Netlify

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
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()],
})
部署命令:
npx netlify deploy

Nitro (Vercel, Railway, Node.js, Docker)

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
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()],
})
构建并启动:
npm run build && node .output/server/index.mjs

Bun

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()]
Bun 部署要求使用 React 19。如果使用 React 18,请采用 Node.js 部署方式。
ts
// vite.config.ts — add bun preset to nitro
plugins: [tanstackStart(), nitro({ preset: 'bun' }), viteReact()]

Selective SSR

选择性 SSR

Control SSR per route with the
ssr
property.
通过
ssr
属性按路由控制 SSR 行为。

ssr: true
(default)

ssr: true
(默认)

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
})
在服务端运行
beforeLoad
和
loader
,在服务端渲染组件:
tsx
export const Route = createFileRoute('/posts/$postId')({
  ssr: true, // default
  loader: () => fetchPost(), // runs on server during SSR
  component: PostPage, // rendered on server
})

ssr: false

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
})
禁用服务端执行
beforeLoad
/
loader
以及服务端渲染:
tsx
export const Route = createFileRoute('/dashboard')({
  ssr: false,
  loader: () => fetchDashboard(), // runs on client only
  component: DashboardPage, // rendered on client only
})

ssr: 'data-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
})
在服务端运行
beforeLoad
/
loader
,但仅在客户端渲染组件:
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
    }
  },
})
根据 params/search 参数在运行时决定是否启用 SSR:
tsx
export const Route = createFileRoute('/docs/$docType/$docId')({
  ssr: ({ params }) => {
    if (params.status === 'success' && params.value.docType === 'sheet') {
      return false
    }
  },
})

SSR Inheritance

SSR 继承规则

Children inherit parent SSR config and can only be MORE restrictive:
  • true
    →
    data-only
    or
    false
    (allowed)
  • false
    →
    true
    (NOT allowed — parent
    false
    wins)
子路由会继承父路由的 SSR 配置,且只能比父路由更严格:
  • true
    →
    data-only
    或
    false
    (允许)
  • false
    →
    true
    (不允许 — 父路由的 false 优先级更高)

Default SSR

默认 SSR 配置

Change the default for all routes in
src/start.ts
:
tsx
import { createStart } from '@tanstack/react-start'

export const startInstance = createStart(() => ({
  defaultSsr: false,
}))
在
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.
在构建时生成静态 HTML:
ts
// vite.config.ts
tanstackStart({
  prerender: {
    enabled: true,
    crawlLinks: true,
    concurrency: 14,
    failOnError: true,
  },
})
静态路由会被自动发现。动态路由(例如
/users/$userId
)需要开启
crawlLinks
或显式配置
pages
。

SEO and Head Management

SEO 与头部管理

Basic Meta Tags

基础 Meta 标签

tsx
export const Route = createFileRoute('/')({
  head: () => ({
    meta: [
      { title: 'My App - Home' },
      { name: 'description', content: 'Welcome to My App' },
    ],
  }),
})
tsx
export const Route = createFileRoute('/')({
  head: () => ({
    meta: [
      { title: 'My App - Home' },
      { name: 'description', content: 'Welcome to My App' },
    ],
  }),
})

Dynamic Meta from Loader Data

基于 Loader 数据的动态 Meta

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 },
    ],
  }),
})
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)

结构化数据(JSON-LD)

tsx
head: ({ loaderData }) => ({
  scripts: [
    {
      type: 'application/ld+json',
      children: JSON.stringify({
        '@context': 'https://schema.org',
        '@type': 'Article',
        headline: loaderData.title,
      }),
    },
  ],
})
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' },
        })
      },
    },
  },
})
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

1. 高:Cloudflare Workers 缺少 nodejs_compat 标志

jsonc
// WRONG — Node.js APIs fail at runtime
{ "compatibility_flags": [] }

// CORRECT
{ "compatibility_flags": ["nodejs_compat"] }
jsonc
// WRONG — Node.js APIs fail at runtime
{ "compatibility_flags": [] }

// CORRECT
{ "compatibility_flags": ["nodejs_compat"] }

2. MEDIUM: Bun deployment with React 18

2. 中:Bun 部署搭配 React 18

Bun-specific deployment only works with React 19. Use Node.js deployment for React 18.
Bun 专属的部署方式仅支持 React 19。React 18 请使用 Node.js 部署。

3. MEDIUM: Child route loosening parent SSR config

3. 中:子路由放宽父路由的 SSR 配置

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
})
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 — API endpoints for sitemaps, robots.txt
  • start-core/execution-model — SSR affects where code runs
  • start-core/server-routes — 用于站点地图、robots.txt 的 API 端点
  • start-core/execution-model — SSR 会影响代码的运行位置