🧱 Next.js 真实项目结构(流式 / Server Actions / 接 AI)¶
全栈篇的"实战进阶"。
fullstack/index.md讲了最小 HTTP 与 CRUD,这里给你一个接近生产的 Next.js App Router 项目骨架:目录分层、Server Component 与 Client Component 边界、流式渲染、Server Actions 写数据、以及如何把 AI 流式聊天 嵌进来。依据 Next.js 官方文档(App Router)。
1. 推荐目录结构¶
app/
(shop)/ # 路由分组(不影响 URL)
products/page.tsx # 列表(Server Component,直接查库)
api/
chat/route.ts # AI 聊天接口(见 AI 实战)
layout.tsx # 根布局
page.tsx # 首页
components/
ProductCard.tsx # 客户端交互组件('use client')
lib/
db.ts # 数据访问(服务端专用,绝不进客户端包)
llm.ts # AI 客户端封装
致命边界:哪些代码能进浏览器
lib/db.ts(含数据库密码/连接串)只能被 Server Component / Route Handler / Server Action 引用。- 一旦在
'use client'组件里import了它,会被打包进前端,密钥泄露。 - 用 Next 的服务端/客户端边界强制隔离:客户端只通过 Route Handler 或 Server Action 间接访问数据。
2. 流式渲染(首屏快 + 渐次出现)¶
// app/products/page.tsx —— Server Component 用 async + Suspense 流式
import { Suspense } from 'react'
async function ProductList() {
const list = await fetchProducts() // 慢查询
return <ul>{
list.map(p => <li key={p.id}>{p.name} - ¥{p.price}</li>)
}</ul>
}
export default function Page() {
return (
<Suspense fallback={<p>加载商品中…</p>}>
<ProductList />
</Suspense>
)
}
流式三件套
asyncServer Component 天然支持流式。Suspense包住慢区域,先出骨架再出内容。- AI 回答用
streamText().toDataStreamResponse()把 token 边生成边推(详见 AI 聊天实战)。
3. Server Actions(不用手写 API 也能写数据)¶
// app/products/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
import { db } from '@/lib/db'
export async function addProduct(formData: FormData) {
const name = String(formData.get('name'))
await db.products.create({ name })
revalidatePath('/products') // 刷新该路由的缓存
}
// 客户端组件直接调用
'use client'
import { addProduct } from './actions'
export function Form() {
return <form action={addProduct}>
<input name="name" />
<button>添加</button>
</form>
}
Server Action 安全
'use server'函数对任何客户端调用都开放,必须自己做权限校验(不能只靠前端隐藏按钮)。- 不要把原始
formData直接进库,服务端要做校验/清洗(Zod 校验最佳)。
4. 把 AI 聊天嵌进全栈项目¶
lib/llm.ts封装 AI 客户端(密钥只在服务端)。app/api/chat/route.ts暴露流式接口(复制 AI 实战 的 route)。- 聊天页用
useChat(@ai-sdk/react)连接口,其余页面照常 SSR。
到此你已具备"Next.js 全栈 + AI 流式"的完整交付能力,正是 2026 招聘最稀缺的组合。
5. 踩坑¶
常见坑
- Server Component 里误用
useEffect/useState→ 报"Hooks 只能在客户端用",加'use client'。 fetch默认会缓存(Next 15 起默认不缓存,但老版要显式{ cache: 'no-store' })。revalidatePath在非 Server Action / Route Handler 上下文不可用。