Skip to main content

Next.js 全栈项目

2025+ 全栈首选:Next.js 15+ App Router 已成为 React 全栈事实标准。Server Components 默认 RSC 渲染,Server Actions 替代传统 API,一个项目搞定前后端 + 部署

一、技术栈选型

维度选型
框架Next.js 15+(App Router)
UIReact 19
语言TypeScript 5+
样式Tailwind CSS v4(首推)/ CSS Modules
组件库shadcn/ui(按需复制,不锁死版本)
状态管理Zustand(客户端)/ React 内置(服务端)
数据获取Server Components + fetch / TanStack Query
ORMPrisma / Drizzle
数据库PostgreSQL(生产)/ SQLite(本地)
鉴权Auth.js (NextAuth v5) / Clerk
表单React Hook Form + Zod
请求/校验Zod(运行时类型校验,前后端共用)
测试Vitest + Playwright(E2E)
部署Vercel(最快)/ Docker(自托管)

二、一键创建

pnpm create next-app@latest my-app
# 交互式选项:
# ✔ Would you like to use TypeScript? … Yes
# ✔ Would you like to use ESLint? … Yes
# ✔ Would you like to use Tailwind CSS? … Yes
# ✔ Would you like your code inside a `src/` directory? … Yes
# ✔ Would you like to use App Router? … Yes
# ✔ Would you like to use Turbopack for `next dev`? … Yes
# ✔ Would you like to customize the import alias (`@/*`)? … No

cd my-app && pnpm dev

Turbopack 是 Rust 编写的下一代打包器,dev 启动比 webpack 快 10x。15+ 已稳定。

三、目录结构

my-app/
├─ public/ # 静态资源
├─ src/
│ ├─ app/ # App Router(核心)
│ │ ├─ (marketing)/ # 路由分组(不影响 URL)
│ │ │ ├─ about/page.tsx
│ │ │ └─ pricing/page.tsx
│ │ ├─ (dashboard)/ # 后台分组
│ │ │ ├─ dashboard/
│ │ │ │ ├─ layout.tsx # 嵌套布局
│ │ │ │ └─ page.tsx
│ │ │ └─ settings/
│ │ ├─ api/ # Route Handlers(替代 pages/api)
│ │ │ └─ users/route.ts
│ │ ├─ login/page.tsx
│ │ ├─ layout.tsx # 根布局(必须有)
│ │ ├─ page.tsx # 根页面(/)
│ │ ├─ loading.tsx # 全局 loading UI
│ │ ├─ error.tsx # 全局 error boundary
│ │ ├─ not-found.tsx # 404
│ │ └─ globals.css
│ ├─ components/ # 通用组件
│ │ ├─ ui/ # shadcn/ui 组件
│ │ └─ ...
│ ├─ lib/ # 工具函数
│ │ ├─ db.ts # Prisma client 单例
│ │ ├─ auth.ts # Auth.js 配置
│ │ └─ utils.ts
│ ├─ server/ # 服务端代码(仅在 server actions/route handlers 用)
│ │ └─ actions/
│ ├─ stores/ # 客户端状态(Zustand)
│ ├─ hooks/ # 自定义 hooks
│ ├─ types/ # 全局类型
│ └─ middleware.ts # 全局中间件
├─ prisma/ # Prisma schema 与迁移
│ └─ schema.prisma
├─ .env.local # 本地环境变量(不 commit)
├─ .env.example # 环境变量模板(要 commit)
├─ next.config.ts
├─ tailwind.config.ts
├─ tsconfig.json
├─ components.json # shadcn/ui 配置
└─ package.json

四、核心配置

4.1 next.config.ts

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
reactStrictMode: true,
// 开启严格模式,重复渲染检查副作用

// 图片域名白名单
images: {
remotePatterns: [{ protocol: "https", hostname: "cdn.example.com" }],
},

// 打包阶段注入环境变量(公开给客户端)
env: {
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
},

// 启用 server actions(15+ 默认开启)
experimental: {
serverActions: {
bodySizeLimit: "2mb",
},
},

// 路径别名(TS 配 paths 后这里也加,否则编辑器报错)
async rewrites() {
return [
{
source: "/api/proxy/:path*",
destination: `${process.env.API_BASE}/:path*`,
},
];
},
};

export default nextConfig;

4.2 tsconfig.json

{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

五、App Router 核心

5.1 Server Component(默认) vs Client Component

// app/dashboard/page.tsx —— 默认是 Server Component(推荐)
import { db } from "@/lib/db";

export default async function DashboardPage() {
// ✅ 直接查数据库,不需要 useEffect
const users = await db.user.findMany();

return (
<div>
<h1>用户列表</h1>
{users.map((u) => (
<div key={u.id}>{u.name}</div>
))}
</div>
);
}
// app/components/Counter.tsx —— 需要交互时显式声明 "use client"
"use client";

import { useState } from "react";

export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}

原则:能 Server 就别 Client。Server Component 直接走数据库,零 JS 体积下发到浏览器

5.2 Server Actions(替代 API)

// app/actions/user.ts
"use server";

import { revalidatePath } from "next/cache";
import { db } from "@/lib/db";

export async function createUser(formData: FormData) {
const name = formData.get("name") as string;
await db.user.create({ data: { name } });
revalidatePath("/dashboard"); // 刷新指定路由
}
// app/components/UserForm.tsx
import { createUser } from "@/app/actions/user";

export function UserForm() {
return (
<form action={createUser}>
<input name="name" />
<button type="submit">提交</button>
</form>
);
}

不用写 onSubmit、不用写 fetch、不用写 loading 状态。这就是 15+ 推荐的写法

5.3 Route Handlers(保留 API 路由,给非表单场景用)

// app/api/users/route.ts
import { NextResponse } from "next/server";
import { db } from "@/lib/db";

export async function GET() {
const users = await db.user.findMany();
return NextResponse.json(users);
}

export async function POST(req: Request) {
const data = await req.json();
const user = await db.user.create({ data });
return NextResponse.json(user, { status: 201 });
}

六、Server Component 加载数据(fetch 缓存)

// 默认行为:build 时静态生成(SSG)
const res = await fetch("https://api.example.com/posts");

// 动态渲染:跳过缓存,每次请求都跑
const res = await fetch("https://api.example.com/posts", { cache: "no-store" });

// ISR:10 分钟重新验证
const res = await fetch("https://api.example.com/posts", {
next: { revalidate: 600 },
});

// 标签失效:配合 revalidateTag 精准刷新
const res = await fetch("https://api.example.com/posts", {
next: { tags: ["posts"] },
});

七、中间件

src/middleware.ts

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(req: NextRequest) {
// 鉴权检查
const token = req.cookies.get("token");
if (!token && req.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", req.url));
}
return NextResponse.next();
}

export const config = {
matcher: ["/dashboard/:path*"], // 只对 dashboard 路径生效
};

八、环境变量

.env.local绝不入仓):

DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
NEXTAUTH_SECRET=xxx
NEXTAUTH_URL=http://localhost:3000
API_BASE=https://api.example.com

.env.example要入仓,作为模板):

DATABASE_URL=
NEXTAUTH_SECRET=
NEXTAUTH_URL=
API_BASE=

客户端能访问的环境变量必须以 NEXT_PUBLIC_ 开头。

九、状态管理(Zustand)

// src/stores/useUserStore.ts
import { create } from "zustand";

interface UserState {
name: string;
setName: (name: string) => void;
}

export const useUserStore = create<UserState>((set) => ({
name: "",
setName: (name) => set({ name }),
}));

十、提交规范

pnpm add -D husky lint-staged @commitlint/cli @commitlint/config-conventional
pnpm husky init

.husky/commit-msg

npx --no-install commitlint --edit "$1"

commitlint.config.js

module.exports = { extends: ["@commitlint/config-conventional"] };

十一、部署

11.1 Vercel(最快)

pnpm i -g vercel
vercel # 首次部署,按提示登录
vercel --prod # 生产部署

Vercel 是 Next.js 亲生爹,零配置部署 + 自动 HTTPS + 全球 CDN。

11.2 Docker(自托管)

Dockerfile

FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile

FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]

next.config.ts 必须开启 standalone 才有 .next/standalone

const nextConfig: NextConfig = {
output: "standalone",
};

十二、避坑指南

  1. 不要在 Client Component 里直接 await 数据库

    "use client";
    // ❌ 错误:客户端没有数据库连接
    const users = await db.user.findMany();

    // ✅ 正确:把数据查询放 Server Component,Client 只接收 props
  2. useEffect 不该在 Server Component 出现:那是 Client 的事。Server 直接 await 即可。

  3. 避免把整个组件树声明 "use client":会丢失 RSC 优势。只在需要交互的最小组件上加

  4. 环境变量敏感信息走服务端NEXT_PUBLIC_*打包到客户端 JS 里,secret 绝不能加这个前缀。

  5. dynamic 强制动态渲染

    import { dynamic } from "@/lib/dynamic";
    export const dynamic = "force-dynamic"; // 跳过缓存
  6. Image 组件必须设 width/heightfill:否则布局抖动(CLS)。

  7. Server Action 限制 1MB:大文件上传走 Route Handlers + 流式。

  8. 静态资源放 public 不会被 hash:构建后文件名不变,有缓存风险,需要 hash 的放 src/assets

十三、面试高频

  • Q:Server Component 和 Client Component 区别? A:Server 在服务端渲染,不发送 JS 到浏览器,可直连数据库;Client 必须 "use client",会 hydrate 走传统 React 生命周期。原则:能 Server 就别 Client

  • Q:App Router 相对 Pages Router 的优势? A:① 嵌套 layout 复用更优雅;② Server Component 默认 RSC;③ Server Actions 替代 API 路由;④ 流式渲染 + Suspense 体验更好;⑤ 路由分组 (group) 不影响 URL。

  • Q:Server Action 安全性怎么保证? A:① 自动加密的 action ID(每次构建变化);② 内置 CSRF 保护(仅 POST);③ 必须经过 middleware;④ 配合 Zod 做参数校验。

  • Q:怎么优化 Next.js 首屏? A:① Server Component 直出数据;② next/image + next/font 自动优化;③ next/dynamic 懒加载大组件;④ 路由级 loading.tsx + <Suspense> 流式渲染。

  • Q:什么时候不该用 Next.js? A:纯内部后台、无 SEO 需求、不需要 SSR/SSG 的场景,用 Vite + React 更轻。Next.js 的优势在于「需要服务端能力 + SEO」。