TanStack Start 中文文档
快速开始

从零搭建项目

注意

如果你已经通过 CLI 创建了项目,或克隆了快速开始指南中的示例,可以跳过本指南,直接进入路由一章。

想从零搭建一个 TanStack Start 项目?

本指南将带你从零构建一个非常基础的 TanStack Start Web 应用。我们将一起用 TanStack Start 实现:

  • 提供一个首页
  • 显示一个计数器
  • 在服务端和客户端递增计数器

这里可以看到最终效果

先创建一个新项目目录并初始化它。

mkdir myApp
cd myApp
npm init -y

注意

下面的示例中统一使用 npm,你也可以换成自己惯用的包管理器。

TypeScript 配置

我们强烈建议在 TanStack Start 中使用 TypeScript。创建一个 tsconfig.json 文件,至少包含以下设置:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "moduleResolution": "Bundler",
    "module": "ESNext",
    "target": "ES2022",
    "skipLibCheck": true,
    "strictNullChecks": true
  }
}

注意

启用 verbatimModuleSyntax 可能导致服务端代码泄漏到客户端打包产物中。建议保持该选项关闭。

安装依赖

TanStack Start 由 TanStack Router 驱动,构建工具支持 ViteRsbuild

安装 Start 和 Router:

npm i @tanstack/react-start @tanstack/react-router

还需要 React:

npm i react react-dom

安装你想使用的构建工具及其 React 集成插件:

npm i -D vite @vitejs/plugin-react

如果你使用 Vite,也可以用 @vitejs/plugin-react-swc 替代 @vitejs/plugin-react

再安装 TypeScript:

npm i -D typescript @types/react @types/react-dom @types/node

更新配置文件

接着更新 package.json,改用你选择的构建工具 CLI,并设置 "type": "module"

{
  // ...
  "type": "module",
  "scripts": {
    "dev": "vite dev",
    "build": "vite build"
  }
}

然后配置 TanStack Start 的构建工具插件:

vite.config.ts
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import viteReact from '@vitejs/plugin-react'

export default defineConfig({
  server: {
    port: 3000,
  },
  resolve: {
    tsconfigPaths: true,
  },
  plugins: [
    tanstackStart(),
    // react's vite plugin must come after start's vite plugin
    viteReact(),
  ],
})

添加基础模板

使用 TanStack Start 需要两个必需文件:

  1. 路由配置(router configuration)
  2. 应用根组件(root)

完成配置后,文件树看起来是这样的:

.
├── src/
│   ├── routes/
│   │   └── `__root.tsx`
│   ├── `router.tsx`
│   ├── `routeTree.gen.ts`
├── `vite.config.ts` or `rsbuild.config.ts`
├── `package.json`
└── `tsconfig.json`

路由配置

这个文件决定了 Start 中使用的 TanStack Router 的行为。你可以在这里配置从默认的预加载功能缓存过期策略的一切。

注意

此时你还没有 routeTree.gen.ts 文件。它会在你首次运行 TanStack Start 时自动生成。

// src/router.tsx
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export function getRouter() {
  const router = createRouter({
    routeTree,
    scrollRestoration: true,
  })

  return router
}

应用根组件

最后,我们需要创建应用的根组件。这是所有其他路由的入口,根组件中的代码会包裹应用里的所有路由。

// src/routes/__root.tsx
import type { ReactNode } from 'react'
import {
  Outlet,
  createRootRoute,
  HeadContent,
  Scripts,
} from '@tanstack/react-router'

export const Route = createRootRoute({
  head: () => ({
    meta: [
      {
        charSet: 'utf-8',
      },
      {
        name: 'viewport',
        content: 'width=device-width, initial-scale=1',
      },
      {
        title: 'TanStack Start Starter',
      },
    ],
  }),
  component: RootComponent,
})

function RootComponent() {
  return (
    <RootDocument>
      <Outlet />
    </RootDocument>
  )
}

function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
  return (
    <html>
      <head>
        <HeadContent />
      </head>
      <body>
        {children}
        <Scripts />
      </body>
    </html>
  )
}

编写你的第一个路由

基础模板搭好之后,就可以写第一个路由了。只需在 src/routes 目录下新建一个文件即可。

// src/routes/index.tsx
import * as fs from 'node:fs'
import { createFileRoute, useRouter } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'

const filePath = 'count.txt'

async function readCount() {
  return parseInt(
    await fs.promises.readFile(filePath, 'utf-8').catch(() => '0'),
  )
}

const getCount = createServerFn({
  method: 'GET',
}).handler(() => {
  return readCount()
})

const updateCount = createServerFn({ method: 'POST' })
  .validator((d: number) => d)
  .handler(async ({ data }) => {
    const count = await readCount()
    await fs.promises.writeFile(filePath, `${count + data}`)
  })

export const Route = createFileRoute('/')({
  component: Home,
  loader: async () => await getCount(),
})

function Home() {
  const router = useRouter()
  const state = Route.useLoaderData()

  return (
    <button
      type="button"
      onClick={() => {
        updateCount({ data: 1 }).then(() => {
          router.invalidate()
        })
      }}
    >
      Add 1 to {state}?
    </button>
  )
}

译者注:上面的代码做了什么?

这个计数器示例演示了 Start 的核心工作方式:createServerFn 创建「服务器函数(Server Functions)」——getCount 读取服务器上的文件,updateCount 写入文件;路由的 loader 在服务端调用 getCount 完成首屏数据加载;点击按钮时客户端调用 updateCount,成功后通过 router.invalidate() 让路由器重新执行加载器刷新数据。整个调用过程都是类型安全的,且服务端代码永远不会进入客户端打包产物。

搞定了!🤯 你已经成功搭建了一个 TanStack Start 项目,并写下了第一个路由。🎉

现在运行 npm run dev 启动服务,然后访问 http://localhost:3000 看看你的路由效果。

想部署你的应用?请参考部署指南

On this page