React Router + shadcn/ui 模板实战:搭建可服务渲染的 React 组件化项目
本文基于仓库中的 templates/react-router-app/README.md 展开,完整讲解 shadcn/ui 官方提供的 React Router 项目模板。该模板将 React Router v7、Vite 8、Tailwind CSS v4 与 TypeScript 预配置为一个可直接运行的起步项目,并默认内置 shadcn/ui 组件体系。读完后,你将掌握:如何在该模板中通过 CLI 添加组件、如何正确引入并使用组件、以及模板背后 SSR、路由与构建配置的实际工作方式,从而把「空项目」快速推进到可交付的应用状态。
模板定位与技术栈
README 对该模板的定义是:一个基于 React、TypeScript 与 shadcn/ui 的新 React Router 项目模板(a template for a new React Router project with React, TypeScript, and shadcn/ui)。它解决的是「从零开始配置 SSR + Tailwind + 组件库」这类繁琐的初始工作,把这些约定固化在模板里。
从 package.json 可以看到模板锁定的技术栈与脚本命令:
| 依赖 | 版本 | 作用 |
|---|---|---|
react-router / @react-router/dev |
7.15.1 | 框架本体与开发工具链(dev/build/typegen) |
@react-router/node、@react-router/serve |
7.15.1 | Node 平台适配层与生产环境静态/SSR 服务 |
react / react-dom |
^19.2.6 | React 19 |
vite |
^8 | 构建工具 |
tailwindcss + @tailwindcss/vite |
^4 | Tailwind CSS v4(Vite 插件方式接入,无需 PostCSS 配置) |
typescript |
^6 | 类型系统 |
isbot |
^5 | 机器人访问检测(React Router SSR 常用能力) |
脚本方面提供了五条命令,覆盖了完整开发生命周期:
"scripts": {
"build": "react-router build",
"dev": "react-router dev",
"start": "react-router-serve ./build/server/index.js",
"typecheck": "react-router typegen && tsc",
"format": "prettier --write \"**/*.{ts,tsx}\""
}
值得注意的两点:
typecheck会先执行react-router typegen再生成.react-router/types下的类型声明(如+types/root、+types/home),然后才运行tsc。这正是 root.tsx 中import type { Route } from "./+types/root"能够被识别的原因。start直接指向./build/server/index.js,说明构建产物是分离的 server 入口,生产环境由@react-router/serve独立托管。
项目结构与关键文件
模板的文件组织遵循 React Router v7 的 app/ 目录约定:
templates/react-router-app/
├── app/
│ ├── routes/
│ │ └── home.tsx # 首页路由,已内置一个 shadcn/ui Button
│ ├── routes.ts # 路由表
│ ├── root.tsx # 根布局 Layout + ErrorBoundary
│ └── app.css # Tailwind 入口
├── public/
│ └── favicon.ico
├── Dockerfile # 多阶段构建部署配置
├── react-router.config.ts # React Router 框架配置
├── tsconfig.json # 含 ~ 路径别名
├── vite.config.ts # Vite 插件链
└── package.json
路由表:routes.ts
app/routes.ts 采用「集中式路由配置」而非文件系统约定路由:
import { type RouteConfig, index } from "@react-router/dev/routes"
export default [index("routes/home.tsx")] satisfies RouteConfig
新增页面时,向该数组追加路由即可,路由结构一目了然,便于团队统一管理。
根布局:root.tsx
app/root.tsx 是 React Router 框架层的约定入口,包含三部分:
Layout组件:渲染完整 HTML 骨架,在<head>中注入<Meta />与<Links />(由各路由导出的meta/links数据驱动),在<body>末尾挂载<ScrollRestoration />(滚动位置恢复)与<Scripts />(水合脚本)。- 默认导出
App:仅返回<Outlet />,作为子路由的挂载点。 ErrorBoundary:模板内置了错误兜底逻辑——通过isRouteErrorResponse(error)区分 404 与一般路由错误并给出不同文案;在开发环境(import.meta.env.DEV)下还会把error.message与完整error.stack渲染到页面上,方便调试。
这套结构让每个新页面天然具备错误边界与滚动恢复能力,无需额外配置。
首页:已预置一个组件示例
app/routes/home.tsx 并非空白页,而是「活文档」:
import { Button } from "~/components/ui/button"
export default function Home() {
return (
<div className="flex min-h-svh p-6">
<div className="flex max-w-md min-w-0 flex-col gap-4 text-sm leading-loose">
<div>
<h1 className="font-medium">Project ready!</h1>
<p>You may now add components and start building.</p>
<p>We've already added the button component for you.</p>
<Button className="mt-2">Button</Button>
</div>
</div>
</div>
)
}
它完成了两件事:一是声明式地告诉开发者「现在可以开始添加组件了」,二是用真实的 Button 组件演示了「引入—使用」的完整闭环,同时其布局类(flex min-h-svh、gap-4、text-sm leading-loose)也示范了 Tailwind v4 在模板中的直接可用性。
添加组件:shadcn CLI 工作流
继承 README 的核心操作步骤:向应用添加 shadcn/ui 组件,运行如下命令:
npx shadcn@latest add button
README 说明该命令会把 ui 组件放置到 components 目录中。结合本模板的结构约定,组件最终落在 app/components/ui/ 下,因为:
- tsconfig.json 定义了路径别名
"~/*": ["./app/*"],即~指向app/目录; - vite.config.ts 开启了
resolve.tsconfigPaths: true,让 Vite 在打包阶段也能解析 tsconfig 中声明的别名,保证 dev/build 行为一致。
因此模板中组件的实际导入写法是:
import { Button } from "~/components/ui/button"
这正是 home.tsx 使用的形式。需要注意 README 中给出的通用写法:
import { Button } from "@/components/ui/button";
这是 shadcn/ui 面向多数框架(Next.js、Vite 等)的 @/ 别名惯例。在本模板里,从 tsconfig.json 的路径映射看,实际生效的别名是 ~ 而非 @。如果你习惯 @/ 前缀,需要自行在 tsconfig.json 的 paths 中补一条 "@/*": ["./app/*"] 映射,Vite 侧已启用 tsconfigPaths 无需改动。
配置解析:让 SSR 与 Tailwind 开箱即用
react-router.config.ts
react-router.config.ts 内容极简但关键:
import type { Config } from "@react-router/dev/config"
export default {
// Config options...
// Server-side render by default, to enable SPA mode set this to `false`
ssr: true,
} satisfies Config
ssr: true 表明模板默认开启服务端渲染;配置文件内注释也明确提示:若希望切换为纯 SPA 模式,将该值改为 false 即可。这对需要 SEO 的服务端渲染场景是默认最优解,对纯客户端应用则留了开关。
vite.config.ts
vite.config.ts 只有四行有效配置:
import { reactRouter } from "@react-router/dev/vite"
import tailwindcss from "@tailwindcss/vite"
import { defineConfig } from "vite"
export default defineConfig({
resolve: { tsconfigPaths: true },
plugins: [tailwindcss(), reactRouter()],
})
两个 Vite 插件各司其职:tailwindcss() 以插件方式处理 Tailwind v4 的 CSS(v4 不再需要 tailwind.config.js 与 PostCSS 配置);reactRouter() 提供开发服务器、预渲染、构建产物拆分等框架能力。resolve.tsconfigPaths 则如前文所述,打通了 ~ 别名在构建时的解析。
app.css
样式入口 app/app.css 只有一行 @import "tailwindcss";,并在 root.tsx 中通过 import "./app.css" 挂到应用全局——这是 Tailwind CSS v4 的标准入口方式,之后所有 className 工具类(如 home.tsx 中的 min-h-svh)即可全局生效。
Docker 部署
模板附带了一份 Dockerfile,采用经典的三阶段 + 生产镜像分层构建:
development-dependencies-env:完整npm ci安装含 devDependencies 的node_modules(供构建使用);production-dependencies-env:仅npm ci --omit=dev安装生产依赖,保证运行镜像最小化;build-env:复用开发依赖执行npm run build,产出./build目录;- 最终镜像:基于
node:20-alpine,只复制package.json、生产依赖与build/产物,CMD ["npm", "run", "start"]即启动react-router-serve ./build/server/index.js。
这套分层策略把构建依赖与运行依赖彻底隔离,最终镜像不包含 devDependencies 与源码,适合生产环境直接部署。
小结
回到 README 本身给出的两条核心操作,现在可以完整理解其背后的工程支撑:
- 添加组件:
npx shadcn@latest add button,组件写入components目录(本模板中即app/components/ui/,配合~别名); - 使用组件:从
~/components/ui/button(或按 README 惯例配置后的@/components/ui/button)导入,在任意路由组件中直接使用。
模板的价值在于把「React Router v7 SSR + Tailwind v4 + TypeScript + shadcn/ui」的初始配置全部预置好——路由表、根布局与错误边界、类型生成、Vite 插件链、Docker 多阶段构建——开发者拿到模板后即可把精力放在业务与组件本身,而不是脚手架配置。如需对比其他框架的起步模板,可以参阅同目录下的 templates/next-app、templates/vite-app 等模板。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00