写给刚上手 Next.js 的你:项目文件怎么分、组件怎么拆、跟 AI 怎么配合,这里都有真实例子照着做。
浅色系 · Mac 风格 · 附问卷项目重构实例
刚接触 Next.js 项目时,大部分人想法都一样:“能跑起来就行”。 可项目一旦从十几个文件长到几十个、上百个,麻烦通常逃不开这三种:
数据、组件全堆在 page.tsx 里,想改一个按钮的圆角,得先翻完 300 行代码才找得到在哪儿。
你说一句“帮我优化一下”,AI 这改一处那改一处,改完你发现——多了个新 bug。
每个页面都自己写一遍按钮样式,翻到第五个页面你就会发现,项目里躺着七八种长得不一样的按钮。
写规范图的不是好看,是让不同的人——包括未来的你,还有帮你写代码的 AI——按同一套规矩干活。 好架构大概长这样:
Next.js 16 继续使用 App Router。在这个体系里,你不用写路由配置文件,
只要按照规则在 app/ 目录下放文件,Next.js 会自动帮你生成路由。
本章以 Next.js 16.2+ 为基线讲解,兼顾 15.x 用户的升级路径。
page.tsx 才会暴露为路由。
其他文件(比如 components.tsx、utils.ts)不会变成 URL。
默认 app/ 可以直接放在项目根目录。但推荐把它放进 src/app/,
这样项目根目录只留下配置文件和文档,业务代码都集中在一起。
Next.js App Router 里,所有组件默认都是服务器组件—— 它们不会跑到浏览器里,是直接在服务器上渲成 HTML 发给用户。带来的结果是:
useState、onClick、window。
什么时候需要加 'use client' 变成客户端组件?
useState 或 useEffectonClick / onChange 事件document / window'use client'。如果 TypeScript 或 Next.js 报错说不能用 hooks,再加。
Next.js 还提供两个很有用的命名约定:
(folderName) 命名,只用于组织代码,不影响 URL。例如 app/(marketing)/page.tsx 仍然是 /。_folderName 命名,Next.js 会忽略它,里面的文件不会变成路由。常用来放通用组件或工具。AI 写代码确实快,但快不代表靠谱。没有一套清楚的规则,它就会“自由发挥”—— 文件乱放、样式重复写、服务器组件和客户端组件也分不清楚。
AGENTS.md 是专门给 AI 读的项目规则。它告诉 AI:
src/、组件怎么分层、数据放哪里)。any、文件顶部写注释、超过 200 行拆分)。不要直接对 AI 说“帮我优化页面”。建议按下面的顺序:
npm run build 验证。文件夹就是 URL。AI 不需要配置路由,只需要把文件放到正确的目录。
一个文件只做一件事。超过 200 行就该拆分,方便 AI 和人类阅读。
数据、UI、状态、计算别揽在一个组件里写。拆开之后,AI 想改错都难。
通用 UI 组件和业务组件分层。同一个 Button 到处都能用。
请按照以下规则重构项目:
1. 把 app/ 移到 src/app/,并更新 tsconfig.json 和 tailwind.config.js。
2. 把问卷数据从 page.tsx 抽到 src/lib/data/questionnaire.ts。
3. 把评分逻辑抽到 src/lib/utils/calculateScore.ts。
4. 在 src/components/questionnaire/ 下拆分组件:
- OptionItem(单个选项)
- QuestionCard(一道题)
- QuestionSection(一个模块)
- ResultView(结果页)
- Questionnaire(总控状态)
5. 每个文件顶部写教学注释,说明文件归属和拆分原因。
6. 改完后运行 npm run build 验证。
7. 不要写 any,使用 @/ 路径别名。
原来的 app/page.tsx 把所有东西写在一个文件里:
问卷数据、状态管理、评分算法、选项渲染、结果页。超过 300 行。
现在我们把它拆成多层,每层只负责一件事。
类型是项目的“契约”。先把数据结构说清楚,后面的数据文件和组件都按这个契约来。
export type QuestionType = 'radio' | 'checkbox'
export type Answer = string | string[]
export type AnswerMap = Record
export type Question = {
id: string
type: QuestionType
label: string
options: string[]
weight?: number
}
export type Section = {
id: string
title: string
gender?: 'male' | 'female'
questions: Question[]
}
问卷内容单独放到数据文件。以后增删题目、调整选项,只要改这个文件, 不用动组件。
import { Section } from '@/lib/types/questionnaire'
export const sections: Section[] = [
{
id: 'base',
title: '📋 基础信息 & 家庭关怀',
questions: [
{ id: 'q1', type: 'radio', label: '1. 您的年龄段是?', options: ['18-30岁', '31-45岁', '46-60岁', '60岁以上'] },
// ...
]
},
// ...
]
评分是一个纯函数:输入问卷和答案,输出分数。它不参与 UI,可以单独测试。
export function calculateScore(sections: Section[], answers: AnswerMap): number {
let totalScore = 0
let totalWeight = 0
sections.forEach(section => {
section.questions.forEach(q => {
if (!q.weight) return
const answer = answers[q.id]
if (!answer) return
let scoreValue = 0
if (Array.isArray(answer)) {
const sum = answer.reduce((acc, val) => {
const idx = q.options.indexOf(val)
return acc + (idx >= 0 ? (idx / (q.options.length - 1)) * 10 : 0)
}, 0)
scoreValue = answer.length > 0 ? sum / answer.length : 0
} else {
const idx = q.options.indexOf(answer)
if (idx >= 0) scoreValue = (idx / (q.options.length - 1)) * 10
}
totalScore += scoreValue * q.weight
totalWeight += q.weight
})
})
return totalWeight > 0 ? Math.round((totalScore / totalWeight) * 10) / 10 : 0
}
单个选项卡片,只负责一个 radio/checkbox 的样式和点击。
一道题,包含题干和所有选项,处理单选/多选切换逻辑。
一个模块,渲染模块标题和题目列表。
总控组件,持有状态、处理提交/重置、组合所有模块。
页面入口现在非常薄:导入数据,传给客户端组件。它自己没有任何交互逻辑。
import { sections } from '@/lib/data/questionnaire'
import { Questionnaire } from '@/components/questionnaire/Questionnaire'
export default function Home() {
return
}
src/lib/data/questionnaire.ts 一处就够了。Next.js 16 不是推倒重来的“新框架”——组件写法、App Router 规则、文件约定基本都没动。 真正变的是几个默认值:构建器换成 Turbopack、请求期 API 全面异步、缓存策略升级、几个老 API 被砍掉。
从 Next.js 16 开始,next dev 和 next build 默认使用 Turbopack,
不再需要 --turbopack 或 --turbo。
next build 会报错;需要迁移到 Turbopack 选项,或显式使用 --webpack。next dev 启动速度上比 16.1 快约 87%。Next.js 15 是“同步兼容过渡期”,16 彻底移除同步访问。 以下 API 只能异步读取:
params(在 page、layout、route、icon、opengraph-image、sitemap 中)searchParams(在 page 中)cookies()、headers()、draftMode()// ❌ Next.js 16 不允许
export default function Page({ params }: { params: { id: string } }) {
return <div>{params.id}</div>
}
// ✅ Next.js 16
export default async function Page({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
return <div>{id}</div>
}
middleware 重命名为 proxy
为了更清晰地表达“网络边界与路由转发”的职责,middleware.ts 被重命名为 proxy.ts。
export function middleware() 仍可用,但已被标记为 deprecated。proxy 只支持 Node.js runtime,不再支持 Edge runtime。skipMiddlewareUrlNormalize → skipProxyUrlNormalize。revalidateTag(tag) 单参数写法废弃,必须写成 revalidateTag(tag, 'max')。updateTag():专用于 Server Action,实现 read-your-writes,修改后立即刷新缓存。refresh():在 Server Action 中刷新客户端 router。unstable_cacheLife / unstable_cacheTag 已稳定为 cacheLife / cacheTag。cacheComponents
不再通过 experimental.ppr: true 或 segment 级 experimental_ppr 启用。
改为在 next.config.js 中配置:
const nextConfig = {
cacheComponents: true,
}
module.exports = nextConfig
+ Client / - Server 标出差异来源。next start --inspect:生产服务器也能挂 Node.js 调试器。<Link> transitionTypes:支持按导航方向/上下文触发不同 View Transition 动画。next/amp、AMP 配置)next lint 命令(改用 ESLint / Biome 直接运行)experimental.dynamicIO、experimental.useCacheparams 或 searchParams 时别忘了加 await。
Next.js 16.2.6 和 React 19.2.6 是目前的安全基线版本。 版本低于这个线,项目很可能还带着已公开的 CVE 在跑,生产环境的风险不是说说而已。
官方安全公告修复了两个高危/中危漏洞:
初始补丁不完整,后续又通过 CVE-2025-67779 完成了完整修复。 这意味着“升级到某个中间版本”可能还不够,必须升到最新补丁版本。
这次是一次大规模协调披露,共修复 13 个安全 advisory,覆盖:
beforeInteractive 脚本等场景。每次开发前或升级后,先执行以下命令:
# 1. 查看 Next.js 实际安装版本
npx next --version
# 2. 查看 React 实际安装版本
cat node_modules/react/package.json | grep '"version"'
# 3. 查看 next 的 CVE 是否有补丁
npm audit
next ≥ 16.2.6react / react-dom ≥ 19.2.6PADC 是一个轻量级的 AI 协作循环。它把“让 AI 写代码”这件事分成四步, 每一步都有明确输出,防止 AI 乱改。
把模糊需求变成清单。例如“帮我优化问卷页面”要拆成:
app/ 到 src/app/,更新 tsconfig 和 tailwind 配置。src/lib/data/questionnaire.ts。src/lib/types/questionnaire.ts。src/lib/utils/calculateScore.ts。npm run build 验证。AI 根据清单执行任务。关键原则是一次只做一步。 每做完一步,确认通过后再做下一步。这样出了问题,也知道是哪一步出的错。
AI 写完代码后,必须运行验证。常见的验证有:
npm run build:检查 Next.js 能否正常打包。npx tsc --noEmit:检查 TypeScript 类型。npm run dev:在浏览器里手动点一遍。把这一轮的经验写进文档。例如:
AGENTS.md 里加一条:迁移 src 时必须同步更新 tailwind.config.js。AI 写代码的速度很快,但快不代表对。测试的作用就是尽早揪出问题—— 不测试的话,很可能等上线了才发现页面根本打不开。
npx next --version
cat node_modules/react/package.json | grep '"version"'
npm audit
先确认实际安装的版本满足安全基线:next ≥ 16.2.6、react ≥ 19.2.6。
再用 npm audit 检查依赖是否存在已知 CVE。
npx tsc --noEmit
检查 TypeScript 类型是否正确。这一步能发现很多 import 错误、字段缺失、类型不匹配。
升级到 Next.js 16 后,同步读取 params / searchParams 也会在这里报错。
npm run build
检查 Next.js 能否成功打包。16 默认使用 Turbopack,能发现路径别名、Tailwind 配置、语法、依赖等问题。
注意:Next.js 16 已移除 next lint,如需静态检查,改用 ESLint / Biome 直接运行。
npm run dev
在浏览器里打开页面,手动检查交互:
先运行 npx next --version 确认版本,再运行 npm run build,确保没有类型错误和语法错误。
修改 tsconfig.json、tailwind.config.js、next.config.js 后,必须重新构建验证。
改了 questionnaire.ts 后,手动点一遍,确认题目和选项显示正确。
改了 calculateScore.ts 后,用已知的答案跑一次,确认分数符合预期。
src/app/xxx/page.tsx
src/components/ui/
src/components/questionnaire/
src/lib/data/questionnaire.ts
src/lib/types/questionnaire.ts
src/lib/utils/calculateScore.ts