技术栈核心
| 层级 | 技术 | 版本要求 |
|---|
| 框架 | Next.js (App Router) | ^16.0.0 |
| 语言 | TypeScript (strict) | ^5.8.2 |
| API | tRPC | ^11.0.0 |
| ORM | Drizzle ORM | ^0.41.0 |
| 认证 | NextAuth.js | ^5.0.0-beta |
| AI SDK | @ai-sdk/openai-compatible | ^2.0.41 |
| 包管理 | pnpm | 必须使用 |
AI SDK 使用规范
服务端路由
// app/api/chat/route.ts
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai-compatible";
export async function POST(req: Request) {
const { messages } = await req.json();
return streamText({
model: openai("deepseek-chat"),
messages,
}).toDataStreamResponse();
}
客户端组件
"use client";
import { useChat } from "ai/react";
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: "/api/chat",
});
// ...
}
禁止事项
- 禁止使用
@ai-sdk/openai,必须使用 @ai-sdk/openai-compatible
Server Action 规范
"use server";
import { createSchema } from "@/lib/schemas";
import { db } from "@/server/db";
export async function createAction(rawInput: unknown) {
const input = createSchema.parse(rawInput); // unknown + zod.parse
return db.insert(table).values(input).returning();
}
数据库规范
Schema 定义 (Drizzle)
import { pgTable, text, timestamp, integer } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: integer().primaryKey().generatedAlwaysAsIdentity(),
name: text().notNull(),
email: text().notNull().unique(),
});
表命名
{project_name}_{table_name} // 全小写,下划线分隔
例: jianli_pro_user, jianli_pro_resume
迁移命令
pnpm db:generate # 生成迁移
pnpm db:migrate # 执行迁移
pnpm db:push # 推送 schema
pnpm db:studio # 打开数据面板
项目结构
src/
├── app/ # Next.js App Router
│ ├── api/ # API 路由
│ │ ├── auth/ # 认证
│ │ └── trpc/ # tRPC
│ └── (routes)/ # 页面
├── components/ui/ # shadcn/ui
├── hooks/ # 自定义 Hooks
├── lib/ # 工具函数 (utils.ts)
├── server/
│ ├── api/ # tRPC 路由
│ │ ├── routers/ # 业务路由
│ │ └── trpc.ts # tRPC 配置
│ ├── auth/ # NextAuth
│ └── db/ # Drizzle
│ ├── index.ts # 数据库连接
│ └── schema.ts # 表结构
├── trpc/ # tRPC 客户端
└── env.js # 环境变量验证 (.js)
tRPC 用法
后端 Router
// src/server/api/routers/post.ts
import { z } from "zod";
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
export const postRouter = createTRPCRouter({
getAll: publicProcedure.query(async ({ ctx }) => {
return ctx.db.query.posts.findMany();
}),
create: publicProcedure
.input(z.object({ title: z.string().min(1) }))
.mutation(async ({ ctx, input }) => {
return ctx.db.insert(posts).values(input).returning();
}),
});
前端调用
"use client";
import { api } from "~/trpc/react";
export function PostList() {
const { data } = api.post.getAll.useQuery();
const create = api.post.create.useMutation();
// ...
}
类型安全配置
tsconfig.json 关键配置
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"verbatimModuleSyntax": true,
"moduleResolution": "Bundler"
}
}
环境变量验证 (env.js)
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
AUTH_SECRET: z.string(),
},
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
AUTH_SECRET: process.env.AUTH_SECRET,
},
emptyStringAsUndefined: true,
});
常用命令
pnpm dev # 开发服务器
pnpm build # 生产构建
pnpm check # Biome 检查
pnpm check:write # Biome 自动修复
pnpm typecheck # TypeScript 检查
pnpm db:generate # 生成 Drizzle 迁移
pnpm db:migrate # 执行迁移
pnpm 源配置
项目级源配置
# 配置国内镜像源
echo "registry=https://registry.npmmirror.com" > .npmrc
# 验证
pnpm config get registry
.npmrc 位置
- 项目根目录
.npmrc(你当前的方式,优先级最高,仅作用于本项目)pnpm
- 工作区目录
.npmrc(含 pnpm-workspace.yaml 的目录)pnpm
- 用户目录
~/.npmrc(全局用户配置)pnpm
常用源地址
| 源 | 地址 |
|---|
| 淘宝镜像 | https://registry.npmmirror.com |
| 官方源 | https://registry.npmjs.org |
| 公司私有源 | https://你的私有源地址 |
图标 (lucide-react)
安装
pnpm add lucide-react
使用
import { IconName } from 'lucide-react'
// 基本使用
<IconName />
// 自定义样式
<IconName size={20} color="red" strokeWidth={1.5} />
查找图标
移动端适配要点
| 要点 | 实现 |
|---|
| 高度 | 使用 h-[100dvh] 替代 100vh |
| 输入框字体 | 不小于 16px 避免 iOS 缩放 |
| 自动滚动 | window.scrollTo({ top: document.body.scrollHeight }) |
禁止模式
| 禁止 | 正确 |
|---|
@ai-sdk/openai | @ai-sdk/openai-compatible |
npm / yarn | pnpm |
tailwind.config.js | @theme in CSS |
axios 直接调用 | @/lib/http 封装 |
服务端组件用 window | useEffect / dynamic import |