从零开始构建项目

注意

如果您选择快速启动一个示例项目或克隆项目,可以跳过本指南,直接进入 学习基础知识 指南。

您想从零开始构建一个 TanStack Start 项目吗?

本指南将帮助您构建一个非常基础的 TanStack Start Web 应用程序。我们将一起使用 TanStack Start 来

  • 提供一个首页...
  • 显示一个计数器...
  • 并有一个按钮可以持久化地增加计数器。

效果如下

让我们创建一个新的项目目录并进行初始化。

shell
mkdir myApp
cd myApp
npm init -y
mkdir myApp
cd myApp
npm init -y

注意

> 在所有这些示例中,我们都使用 npm,但您也可以使用您选择的包管理器。

TypeScript 配置

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

json
{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxImportSource": "solid-js",
    "moduleResolution": "Bundler",
    "module": "ESNext",
    "target": "ES2022",
    "skipLibCheck": true,
    "strictNullChecks": true
  }
}
{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxImportSource": "solid-js",
    "moduleResolution": "Bundler",
    "module": "ESNext",
    "target": "ES2022",
    "skipLibCheck": true,
    "strictNullChecks": true
  }
}

注意

> 启用 verbatimModuleSyntax 可能会导致服务器包泄漏到客户端包中。建议保持此选项禁用。

安装依赖

TanStack Start 由 ViteTanStack Router 提供支持,并将它们作为依赖项。

要安装它们,请运行

shell
npm i @tanstack/solid-start @tanstack/solid-router vite
npm i @tanstack/solid-start @tanstack/solid-router vite

您还需要 Solid

shell
npm i solid-js
npm i solid-js

以及一些 TypeScript

shell
npm i -D typescript vite-tsconfig-paths
npm i -D typescript vite-tsconfig-paths

更新配置文件

然后我们将更新我们的 package.json 以使用 Vite 的 CLI 并设置 "type": "module"

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

然后在 vite.config.ts 中配置 TanStack Start 的 Vite 插件

注意

TanStack Start 将停止自动配置 React/Solid Vite 插件。您将拥有完全的控制权——选择 vite-plugin-solid。设置 customViteSolidPlugin: true 以立即选择此功能!

ts
// vite.config.ts
import { defineConfig } from 'vite'
import tsConfigPaths from 'vite-tsconfig-paths'
import { tanstackStart } from '@tanstack/solid-start/plugin/vite'
import viteSolid from 'vite-plugin-solid'

export default defineConfig({
  server: {
    port: 3000,
  },
  plugins: [
    tsConfigPaths(),
    tanstackStart({ customViteSolidPlugin: true }),
    viteSolid({ ssr: true }),
  ],
})
// vite.config.ts
import { defineConfig } from 'vite'
import tsConfigPaths from 'vite-tsconfig-paths'
import { tanstackStart } from '@tanstack/solid-start/plugin/vite'
import viteSolid from 'vite-plugin-solid'

export default defineConfig({
  server: {
    port: 3000,
  },
  plugins: [
    tsConfigPaths(),
    tanstackStart({ customViteSolidPlugin: true }),
    viteSolid({ ssr: true }),
  ],
})

添加基础模板

TanStack Start 的使用需要 2 个必需文件

  1. 路由配置
  2. 您的应用程序的根文件

配置完成后,我们的文件树将如下所示

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

路由配置

这是决定 Start 中 TanStack Router 行为的文件。在这里,您可以配置从默认的 预加载功能缓存陈旧度 的所有内容。

注意

此时您还没有 routeTree.gen.ts 文件。当您第一次运行 TanStack Start 时,该文件将被生成。

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

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

  return router
}

declare module '@tanstack/solid-router' {
  interface Register {
    router: ReturnType<typeof createRouter>
  }
}
// src/router.tsx
import { createRouter as createTanStackRouter } from '@tanstack/solid-router'
import { routeTree } from './routeTree.gen'

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

  return router
}

declare module '@tanstack/solid-router' {
  interface Register {
    router: ReturnType<typeof createRouter>
  }
}

注意

TanStack Start 提供了默认的服务器和客户端入口点来处理请求和客户端入口 + 水合。您可以通过在项目根目录添加 server.ts 和/或 client.tsx 文件来自定义这些入口点,但目前我们将使用默认设置。

应用程序的根目录

最后,我们需要创建应用程序的根文件。这是所有其他路由的入口点。此文件中的代码将包装应用程序中的所有其他路由。

tsx
// src/routes/__root.tsx
import {
  Outlet,
  createRootRoute,
  HeadContent,
  Scripts,
} from '@tanstack/solid-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 <Outlet />
}
// src/routes/__root.tsx
import {
  Outlet,
  createRootRoute,
  HeadContent,
  Scripts,
} from '@tanstack/solid-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 <Outlet />
}

编写您的第一个路由

现在我们已经设置了基础模板,我们可以编写第一个路由了。这可以通过在 app/routes 目录中创建一个新文件来完成。

tsx
// src/routes/index.tsx
import * as fs from 'node:fs'
import { createFileRoute, useRouter } from '@tanstack/solid-router'
import { createServerFn } from '@tanstack/solid-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>
  )
}
// src/routes/index.tsx
import * as fs from 'node:fs'
import { createFileRoute, useRouter } from '@tanstack/solid-router'
import { createServerFn } from '@tanstack/solid-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>
  )
}

就是这样!🤯 您现在已经设置了一个 TanStack Start 项目并编写了您的第一个路由。🎉

您现在可以运行 npm run dev 来启动您的服务器,然后导航到 https://:3000 来查看您的路由运行情况。

您想部署您的应用程序吗?请查看 托管指南

我们的合作伙伴
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
Prisma
订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。

订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。