Note
如果选择通过示例或克隆项目快速启动,可以跳过本指南,直接进入基础知识学习指南。
想要从零开始构建一个 TanStack Start 项目吗?
本指南将帮助你构建一个非常基础的 TanStack Start 网络应用。我们将一起使用 TanStack Start 完成以下功能:
首先创建一个新项目目录并初始化:
mkdir myApp
cd myApp
npm init -y
mkdir myApp
cd myApp
npm init -y
Note
示例中使用的是 npm,但你可以选择自己喜欢的包管理器替代。
强烈建议在 TanStack Start 中使用 TypeScript。创建一个 tsconfig.json 文件,至少包含以下配置:
{
"compilerOptions": {
"jsx": "react-jsx",
"moduleResolution": "Bundler",
"module": "ESNext",
"target": "ES2022",
"skipLibCheck": true,
"strictNullChecks": true
}
}
{
"compilerOptions": {
"jsx": "react-jsx",
"moduleResolution": "Bundler",
"module": "ESNext",
"target": "ES2022",
"skipLibCheck": true,
"strictNullChecks": true
}
}
Note
启用 verbatimModuleSyntax 可能导致服务端代码泄漏到客户端包中,建议保持此选项禁用。
TanStack Start(当前版本*)基于 Vinxi 和 TanStack Router,需要安装这些依赖。
Note
*在 1.0.0 版本发布前,Vinxi 将被移除,TanStack 将仅依赖 Vite 和 Nitro。使用 Vinxi 的命令和 API 可能会被 Vite 插件或专用的 TanStack Start CLI 替代。
运行以下命令安装:
npm i @tanstack/react-start @tanstack/react-router vinxi
npm i @tanstack/react-start @tanstack/react-router vinxi
还需要安装 React 和 Vite React 插件:
npm i react react-dom
npm i -D @vitejs/plugin-react vite-tsconfig-paths
npm i react react-dom
npm i -D @vitejs/plugin-react vite-tsconfig-paths
以及 TypeScript 相关依赖:
npm i -D typescript @types/react @types/react-dom
npm i -D typescript @types/react @types/react-dom
更新 package.json,使用 Vinxi 的 CLI 并设置 "type": "module":
{
// ...
"type": "module",
"scripts": {
"dev": "vinxi dev",
"build": "vinxi build",
"start": "vinxi start"
}
}
{
// ...
"type": "module",
"scripts": {
"dev": "vinxi dev",
"build": "vinxi build",
"start": "vinxi start"
}
}
然后配置 TanStack Start 的 app.config.ts 文件:
// app.config.ts
import { defineConfig } from '@tanstack/react-start/config'
import tsConfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
vite: {
plugins: [
tsConfigPaths({
projects: ['./tsconfig.json'],
}),
],
},
})
// app.config.ts
import { defineConfig } from '@tanstack/react-start/config'
import tsConfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
vite: {
plugins: [
tsConfigPaths({
projects: ['./tsconfig.json'],
}),
],
},
})
TanStack Start 需要四个核心文件:
配置完成后,文件结构如下:
.
├── app/
│ ├── routes/
│ │ └── `__root.tsx`
│ ├── `client.tsx`
│ ├── `router.tsx`
│ ├── `routeTree.gen.ts`
│ └── `ssr.tsx`
├── `.gitignore`
├── `app.config.ts`
├── `package.json`
└── `tsconfig.json`
.
├── app/
│ ├── routes/
│ │ └── `__root.tsx`
│ ├── `client.tsx`
│ ├── `router.tsx`
│ ├── `routeTree.gen.ts`
│ └── `ssr.tsx`
├── `.gitignore`
├── `app.config.ts`
├── `package.json`
└── `tsconfig.json`
此文件定义 Start 中使用的 TanStack Router 行为,可配置从默认的预加载功能到缓存过期策略等所有内容。
Note
初始时不会有 routeTree.gen.ts 文件,首次运行 TanStack Start 时会自动生成。
// app/router.tsx
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
export function createRouter() {
const router = createTanStackRouter({
routeTree,
scrollRestoration: true,
})
return router
}
declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof createRouter>
}
}
// app/router.tsx
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
export function createRouter() {
const router = createTanStackRouter({
routeTree,
scrollRestoration: true,
})
return router
}
declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof createRouter>
}
}
由于 TanStack Start 是服务端渲染 (SSR)框架,需要将路由信息传递到服务端入口:
// app/ssr.tsx
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/react-start/server'
import { getRouterManifest } from '@tanstack/react-start/router-manifest'
import { createRouter } from './router'
export default createStartHandler({
createRouter,
getRouterManifest,
})(defaultStreamHandler)
// app/ssr.tsx
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/react-start/server'
import { getRouterManifest } from '@tanstack/react-start/router-manifest'
import { createRouter } from './router'
export default createStartHandler({
createRouter,
getRouterManifest,
})(defaultStreamHandler)
现在需要一种方式在路由解析到客户端后激活客户端 JavaScript:
// app/client.tsx
/// <reference types="vinxi/types/client" />
import { hydrateRoot } from 'react-dom/client'
import { StartClient } from '@tanstack/react-start'
import { createRouter } from './router'
const router = createRouter()
hydrateRoot(document, <StartClient router={router} />)
// app/client.tsx
/// <reference types="vinxi/types/client" />
import { hydrateRoot } from 'react-dom/client'
import { StartClient } from '@tanstack/react-start'
import { createRouter } from './router'
const router = createRouter()
hydrateRoot(document, <StartClient router={router} />)
最后创建应用的根组件,它将包裹所有其他路由:
// app/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>
)
}
// app/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>
)
}
基础模板设置完成后,可以在 app/routes 目录中创建第一个路由:
// app/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()
})
}}
>
当前值 {state},点击加 1?
</button>
)
}
// app/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()
})
}}
>
当前值 {state},点击加 1?
</button>
)
}
大功告成!🤯 你已经成功搭建了 TanStack Start 项目并编写了第一个路由。🎉
现在可以运行 npm run dev 启动服务器,访问 http://localhost:3000 查看效果。
想要部署应用?请查看托管指南。
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.