Next.js 16 面试题汇总
聚焦 Next.js 16 最新特性:Cache Components、proxy.ts、增强路由、新缓存 API,排除已废弃的 middleware.ts 和 Pages Router 过时用法
目录
- 基础概念
- Cache Components 缓存组件
- proxy.ts 与请求拦截
- App Router 与路由系统
- 缓存 API
- Server Components 与 Client Components
- Server Actions
- 图片与性能优化
- API Routes
- 部署与配置
- 高级特性
基础概念
1. Next.js 16 有哪些重大变化?
答案要点:
- Cache Components: 使用
"use cache"指令显式控制缓存,默认全部动态渲染 - proxy.ts 替代 middleware.ts: 运行在 Node.js runtime,命名更清晰
- 增强路由: Layout 去重、增量预取、智能缓存失效
- 新缓存 API:
revalidateTag()需要 cacheLife 参数,新增updateTag()API - Turbopack 稳定: 开发和生产环境默认使用 Turbopack
- React 19.2: 支持 React 19.2 新特性
追问: Cache Components 与之前的隐式缓存有什么区别?
2. 什么是 Cache Components?为什么要引入它?
答案要点:
- Cache Components 是 Next.js 16 的核心特性,使用
"use cache"指令显式缓存 - 默认动态渲染: 所有代码默认在请求时执行,更符合开发者预期
- 显式缓存: 需要缓存时主动添加
"use cache",避免意外缓存 - 自动生成缓存键: 编译器自动生成缓存键,无需手动管理
- 完善 PPR: 配合 Partial Prerendering,实现细粒度缓存控制
代码示例:
// 启用 Cache Components
// next.config.ts
const nextConfig = {
cacheComponents: true,
}
export default nextConfig
// 缓存整个页面
"use cache"
export default async function Page() {
const posts = await db.post.findMany()
return <div>{posts.map(p => <Post key={p.id} post={p} />)}</div>
}
// 缓存单个组件
async function CachedComponent() {
"use cache"
const data = await fetch('https://api.example.com/data')
return <div>{data}</div>
}
// 缓存函数
async function getCachedData() {
"use cache"
return await db.query()
}追问: Cache Components 与 React Server Components 的关系是什么?
3. proxy.ts 是什么?与 middleware.ts 有什么区别?
答案要点:
- proxy.ts: Next.js 16 引入,替代 middleware.ts,运行在 Node.js runtime
- middleware.ts: 已废弃,仅用于 Edge runtime 场景,未来版本将移除
- 命名更清晰: proxy.ts 明确表示网络边界拦截
- 单一运行时: 统一使用 Node.js runtime,更可预测
代码示例:
// proxy.ts (Next.js 16 推荐)
import { NextRequest, NextResponse } from 'next/server'
export default function proxy(request: NextRequest) {
// 认证检查
const token = request.cookies.get('token')
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
// 添加自定义响应头
const response = NextResponse.next()
response.headers.set('x-custom-header', 'value')
return response
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*']
}迁移步骤:
- 重命名
middleware.ts→proxy.ts - 重命名导出函数为
proxy - 逻辑保持不变
追问: 什么场景下还需要使用 middleware.ts?
4. Next.js 16 的默认行为有哪些变化?
答案要点:
- 默认动态渲染: 所有页面默认在请求时执行,不再隐式缓存
- Turbopack 默认启用:
next dev和next build默认使用 Turbopack - params/searchParams 必须 await: 路由参数和查询参数现在是 Promise
- cookies/headers/draftMode 必须 await: 这些 API 现在返回 Promise
- revalidateTag 需要 cacheLife: 必须提供第二个参数指定缓存策略
- 图片质量默认值变化:
images.qualities默认为[75]
代码示例:
// ✅ Next.js 16 正确写法
export default async function Page({ params, searchParams }) {
const { id } = await params
const { q } = await searchParams
const cookieStore = await cookies()
const headersList = await headers()
return <div>Post {id}</div>
}
// ❌ Next.js 15 及之前的写法(已废弃)
export default function Page({ params, searchParams }) {
const { id } = params // 不再支持
return <div>Post {id}</div>
}追问: 为什么这些 API 要改成异步的?
Cache Components 缓存组件
5. 如何使用 "use cache" 指令?
答案要点:
"use cache"可用于页面、组件、函数- 编译器自动生成缓存键
- 支持细粒度缓存控制
- 配合 Suspense 实现 Partial Prerendering
代码示例:
// 页面级缓存
"use cache"
export default async function BlogPage() {
const posts = await db.post.findMany()
return <PostList posts={posts} />
}
// 组件级缓存
async function ExpensiveComponent({ userId }) {
"use cache"
const data = await fetchUserData(userId)
return <UserProfile data={data} />
}
// 函数级缓存
async function getCachedPosts() {
"use cache"
return await db.post.findMany()
}
// 配合 Suspense 实现 PPR
export default function Page() {
return (
<div>
<StaticHeader />
<Suspense fallback={<Skeleton />}>
<DynamicContent /> {/* 动态部分 */}
</Suspense>
<CachedFooter /> {/* 缓存部分 */}
</div>
)
}
async function CachedFooter() {
"use cache"
const links = await getFooterLinks()
return <footer>{links.map(l => <a key={l.id} href={l.url}>{l.text}</a>)}</footer>
}追问: "use cache" 与 fetch 的 cache 选项有什么区别?
6. Cache Components 如何配置缓存策略?
答案要点:
- 使用
cacheLife配置缓存生命周期 - 内置预设:
'max'、'hours'、'days' - 支持自定义缓存配置
代码示例:
// next.config.ts
const nextConfig = {
cacheComponents: true,
cacheLife: {
// 自定义缓存配置
blog: {
stale: 3600, // 1小时后标记为过期
revalidate: 7200, // 2小时后重新验证
expire: 86400, // 24小时后失效
},
},
}
// 使用内置预设
async function CachedComponent() {
"use cache"
cacheLife('max') // 使用最大缓存时间
const data = await fetch('https://api.example.com/data')
return <div>{data}</div>
}
// 使用自定义配置
async function BlogPost() {
"use cache"
cacheLife('blog') // 使用自定义的 blog 配置
const post = await getPost()
return <article>{post.content}</article>
}追问: stale、revalidate、expire 三个参数的区别是什么?
proxy.ts 与请求拦截
7. 如何在 proxy.ts 中实现认证?
答案要点:
- 检查 cookie 或 token
- 未认证时重定向到登录页
- 可以修改请求和响应
代码示例:
// proxy.ts
import { NextRequest, NextResponse } from 'next/server'
export default function proxy(request: NextRequest) {
const token = request.cookies.get('auth-token')
const { pathname } = request.nextUrl
// 保护的路由
const protectedPaths = ['/dashboard', '/profile', '/settings']
const isProtected = protectedPaths.some(path => pathname.startsWith(path))
if (isProtected && !token) {
// 重定向到登录页,并保存原始 URL
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('from', pathname)
return NextResponse.redirect(loginUrl)
}
// 验证 token(简化示例)
if (token && !isValidToken(token.value)) {
const response = NextResponse.redirect(new URL('/login', request.url))
response.cookies.delete('auth-token')
return response
}
return NextResponse.next()
}
function isValidToken(token: string): boolean {
// 实际应用中应该验证 JWT 等
return token.length > 0
}追问: proxy.ts 与 Server Actions 在认证场景下如何配合?
8. 如何在 proxy.ts 中实现国际化?
答案要点:
- 检测用户语言偏好
- 重定向到对应语言路径
- 设置语言 cookie
代码示例:
// proxy.ts
import { NextRequest, NextResponse } from 'next/server'
const locales = ['en', 'zh', 'ja', 'ko']
const defaultLocale = 'en'
function getLocale(request: NextRequest): string {
// 1. 从 cookie 获取
const localeCookie = request.cookies.get('locale')?.value
if (localeCookie && locales.includes(localeCookie)) {
return localeCookie
}
// 2. 从 Accept-Language 头获取
const acceptLanguage = request.headers.get('accept-language')
if (acceptLanguage) {
const preferredLocale = acceptLanguage
.split(',')[0]
.split('-')[0]
.toLowerCase()
if (locales.includes(preferredLocale)) {
return preferredLocale
}
}
return defaultLocale
}
export default function proxy(request: NextRequest) {
const { pathname } = request.nextUrl
// 检查路径是否已包含语言前缀
const pathnameHasLocale = locales.some(
locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
)
if (pathnameHasLocale) {
return NextResponse.next()
}
// 获取用户语言并重定向
const locale = getLocale(request)
const newUrl = new URL(`/${locale}${pathname}`, request.url)
const response = NextResponse.redirect(newUrl)
response.cookies.set('locale', locale, { maxAge: 31536000 }) // 1年
return response
}
export const config = {
matcher: ['/((?!_next|api|favicon.ico|.*\\..*).*)']
}追问: 如何实现语言切换功能?
App Router 与路由系统
9. Next.js 16 的路由增强有哪些改进?
答案要点:
- Layout 去重: 共享布局只下载一次,不再重复下载
- 增量预取: 只预取未缓存的部分,减少网络传输
- 智能缓存失效: 数据失效时自动重新预取
- 取消机制: 链接离开视口时取消预取请求
- 优先级优化: hover 或重新进入视口时优先预取
代码示例:
// 自动优化的预取
import Link from 'next/link'
export default function ProductList({ products }) {
return (
<div>
{/* 50个产品链接,共享布局只下载一次 */}
{products.map(product => (
<Link key={product.id} href={`/product/${product.id}`}>
{product.name}
</Link>
))}
</div>
)
}
// 控制预取行为
<Link href="/about" prefetch={false}>
About {/* 不预取 */}
</Link>
<Link href="/blog" prefetch={true}>
Blog {/* 立即预取 */}
</Link>追问: 如何监控预取的性能影响?
10. 如何实现动态路由?
答案要点:
- 使用
[param]文件夹创建动态路由 - Next.js 16 变化: params 必须 await
- 使用
generateStaticParams预生成静态路径
代码示例:
// app/blog/[slug]/page.tsx
export default async function BlogPost({ params }) {
// ✅ Next.js 16: 必须 await params
const { slug } = await params
const post = await getPost(slug)
return <article>{post.content}</article>
}
// 预生成静态路径
export async function generateStaticParams() {
const posts = await getAllPosts()
return posts.map((post) => ({
slug: post.slug,
}))
}
// 嵌套动态路由
// app/blog/[category]/[slug]/page.tsx
export default async function Post({ params }) {
const { category, slug } = await params
return <div>{category}/{slug}</div>
}追问: generateStaticParams 与 Cache Components 如何配合?
缓存 API
11. revalidateTag() 在 Next.js 16 中有什么变化?
答案要点:
- 必须提供 cacheLife 参数: 第二个参数指定缓存策略
- 支持 SWR 行为: 用户立即获得缓存数据,后台重新验证
- 内置预设:
'max'、'hours'、'days' - 推荐使用 'max': 适合大多数场景
代码示例:
import { revalidateTag } from 'next/cache'
// ✅ Next.js 16 正确写法
export async function updatePost(id: string) {
await db.post.update(id, data)
// 使用内置预设(推荐)
revalidateTag('blog-posts', 'max')
// 或使用其他预设
revalidateTag('news-feed', 'hours')
revalidateTag('analytics', 'days')
// 或使用自定义配置
revalidateTag('products', { expire: 3600 })
}
// ❌ Next.js 15 写法(已废弃)
revalidateTag('blog-posts') // 缺少第二个参数SWR 行为说明:
- 用户请求时立即返回缓存数据
- Next.js 在后台重新验证数据
- 下次请求获得更新后的数据
追问: revalidateTag 与 updateTag 有什么区别?
12. updateTag() 是什么?何时使用?
答案要点:
- Next.js 16 新增 API: 仅用于 Server Actions
- read-your-writes 语义: 立即失效并刷新缓存
- 用户立即看到变化: 适合需要即时反馈的场景
- 与 revalidateTag 的区别: updateTag 立即刷新,revalidateTag 后台刷新
代码示例:
'use server'
import { updateTag } from 'next/cache'
// 用户资料更新 - 需要立即看到变化
export async function updateUserProfile(userId: string, profile: Profile) {
await db.users.update(userId, profile)
// 立即失效并刷新 - 用户马上看到更新
updateTag(`user-${userId}`)
}
// 对比: 博客文章更新 - 可以容忍延迟
export async function updateBlogPost(postId: string, content: string) {
await db.posts.update(postId, content)
// 后台刷新 - 用户先看到旧数据,后台更新
revalidateTag('blog-posts', 'max')
}使用场景对比:
| API | 刷新时机 | 用户体验 | 适用场景 |
|---|---|---|---|
updateTag() | 立即刷新 | 立即看到变化 | 用户资料、购物车、订单 |
revalidateTag() | 后台刷新 | 先看缓存,后台更新 | 博客、新闻、产品列表 |
追问: updateTag 的性能影响是什么?
13. 如何使用 cacheLife 配置?
答案要点:
- 三个时间参数: stale、revalidate、expire
- 内置预设: 'max'、'hours'、'days'
- 自定义配置: 在 next.config.ts 中定义
代码示例:
// next.config.ts
const nextConfig = {
cacheLife: {
// 自定义配置
blog: {
stale: 900, // 15分钟后标记为过期
revalidate: 3600, // 1小时后重新验证
expire: 86400, // 24小时后完全失效
},
products: {
stale: 300, // 5分钟
revalidate: 900, // 15分钟
expire: 3600, // 1小时
},
},
}
// 使用配置
async function BlogPost() {
"use cache"
cacheLife('blog') // 使用自定义的 blog 配置
const post = await getPost()
return <article>{post.content}</article>
}
// 内联配置
async function ProductList() {
"use cache"
cacheLife({
stale: 60,
revalidate: 300,
expire: 3600
})
const products = await getProducts()
return <div>{products.map(p => <Product key={p.id} {...p} />)}</div>
}参数说明:
- stale: 数据被标记为过期的时间,但仍可使用
- revalidate: 触发后台重新验证的时间
- expire: 数据完全失效,必须重新获取
追问: 如何选择合适的 cacheLife 配置?
Server Components 与 Client Components
14. Server Components 在 Next.js 16 中有什么变化?
答案要点:
- 默认行为: 所有组件默认是 Server Components
- 配合 Cache Components: 使用 "use cache" 控制缓存
- async/await 支持: 直接在组件中获取数据
- 性能优势: 零客户端 JavaScript
代码示例:
// Server Component (默认)
export default async function Page() {
// 直接访问数据库
const posts = await db.post.findMany()
return (
<div>
{posts.map(post => (
<PostCard key={post.id} post={post} />
))}
</div>
)
}
// 配合 Cache Components
async function CachedPosts() {
"use cache"
cacheLife('max')
const posts = await db.post.findMany()
return <PostList posts={posts} />
}
// 混合使用 Server 和 Client Components
export default function Page() {
return (
<div>
<ServerHeader /> {/* Server Component */}
<ClientCounter /> {/* Client Component */}
<ServerFooter /> {/* Server Component */}
</div>
)
}追问: 如何决定哪些组件应该是 Client Components?
15. Client Components 何时使用?
答案要点:
- 需要交互: 事件处理、状态管理
- 使用浏览器 API: localStorage、window 等
- 使用 React Hooks: useState、useEffect 等
- 使用 'use client' 指令: 文件顶部声明
代码示例:
// Client Component
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
)
}
// 组合使用
'use client'
import { useState } from 'react'
export default function SearchPage({ initialResults }) {
const [query, setQuery] = useState('')
const [results, setResults] = useState(initialResults)
async function handleSearch() {
const data = await fetch(`/api/search?q=${query}`)
setResults(await data.json())
}
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<button onClick={handleSearch}>Search</button>
<Results data={results} />
</div>
)
}追问: 如何最小化 Client Components 的使用?
Server Actions
16. Server Actions 在 Next.js 16 中有什么增强?
答案要点:
- 配合 updateTag: 实现即时缓存更新
- 表单处理: 使用 useFormStatus 获取状态
- 错误处理: 使用 useFormState 处理错误
- 乐观更新: 使用 useOptimistic
代码示例:
// Server Action
'use server'
import { updateTag } from 'next/cache'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
const post = await db.post.create({
data: { title, content }
})
// 立即更新缓存
updateTag('posts')
return { success: true, post }
}
// Client Component 使用
'use client'
import { useFormStatus, useFormState } from 'react-dom'
import { createPost } from './actions'
function SubmitButton() {
const { pending } = useFormStatus()
return (
<button type="submit" disabled={pending}>
{pending ? 'Creating...' : 'Create Post'}
</button>
)
}
export default function CreatePostForm() {
const [state, formAction] = useFormState(createPost, null)
return (
<form action={formAction}>
<input name="title" required />
<textarea name="content" required />
<SubmitButton />
{state?.success && <p>Post created!</p>}
</form>
)
}追问: Server Actions 与 API Routes 如何选择?
图片与性能优化
17. Next.js 16 的图片优化有什么变化?
答案要点:
- 默认质量变化:
images.qualities默认为[75] - Image 组件: 自动优化、懒加载、防止 CLS
- Turbopack 加速: 开发和生产环境性能提升
代码示例:
import Image from 'next/image'
export default function Page() {
return (
<div>
{/* 本地图片 */}
<Image
src="/hero.png"
alt="Hero"
width={800}
height={600}
priority // 禁用懒加载
/>
{/* 远程图片 */}
<Image
src="https://example.com/photo.jpg"
alt="Photo"
width={800}
height={600}
quality={90} // 自定义质量
/>
</div>
)
}追问: 如何配置远程图片域名?
18. Turbopack 带来了哪些性能提升?
答案要点:
- Next.js 16 默认启用:
next dev和next build默认使用 - 更快的开发服务器: HMR 速度大幅提升
- 更快的生产构建: 构建时间显著减少
- 文件系统缓存: Next.js 16.1 引入,进一步加速
代码示例:
# Next.js 16 默认使用 Turbopack
npm run dev
# 如果需要使用 Webpack(不推荐)
next build --webpack追问: Turbopack 与 Webpack 的主要区别是什么?
API Routes
19. API Routes 在 Next.js 16 中有什么变化?
答案要点:
- 基本用法不变: 仍在
app/api目录下创建route.ts - 推荐使用 Server Actions: 对于数据变更操作
- API Routes 适用场景: 第三方 webhook、RESTful API
代码示例:
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
const posts = await db.post.findMany()
return NextResponse.json({ posts })
}
export async function POST(request: NextRequest) {
const body = await request.json()
const post = await db.post.create({ data: body })
return NextResponse.json({ post }, { status: 201 })
}追问: 何时使用 API Routes,何时使用 Server Actions?
部署与配置
20. next.config.ts 在 Next.js 16 中有什么新特性?
答案要点:
- 原生 TypeScript 支持: 使用
--experimental-next-config-strip-types标志 - cacheComponents 配置: 启用 Cache Components
- cacheLife 配置: 自定义缓存策略
代码示例:
// next.config.ts
const nextConfig = {
// 启用 Cache Components
cacheComponents: true,
// 自定义缓存策略
cacheLife: {
blog: {
stale: 900,
revalidate: 3600,
expire: 86400,
},
},
// 图片配置
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
},
],
qualities: [75], // 默认值
},
}
export default nextConfig追问: 如何在 TypeScript 配置中使用类型提示?
高级特性
21. Next.js DevTools MCP 是什么?
答案要点:
- Next.js 16 新增: Model Context Protocol 集成
- AI 辅助调试: 提供上下文信息给 AI 代理
- 统一日志: 浏览器和服务器日志集中显示
- 自动错误访问: 详细堆栈跟踪
功能特性:
- Next.js 知识库(路由、缓存、渲染)
- 统一的浏览器和服务器日志
- 自动访问详细错误堆栈
- 页面感知的上下文理解
追问: 如何启用 Next.js DevTools MCP?
22. Next.js 16 废弃了哪些功能?
答案要点:
已移除:
useAmp和 AMP 支持experimental.turbopack配置(现在默认启用)experimental.ppr配置(改用 cacheComponents)serverRuntimeConfig和publicRuntimeConfig
已废弃(未来将移除):
middleware.ts(改用 proxy.ts)next/legacy/image(改用 next/image)images.domains(改用 images.remotePatterns)- 单参数
revalidateTag()(需要 cacheLife 参数)
行为变化:
- params/searchParams 必须 await
- cookies()/headers()/draftMode() 必须 await
- revalidateTag() 需要第二个参数
追问: 如何迁移使用了废弃功能的项目?
总结
Next.js 16 核心变化:
- ✅ Cache Components: 显式缓存,默认动态渲染,使用 "use cache" 指令
- ✅ proxy.ts: 替代 middleware.ts,Node.js runtime,命名更清晰
- ✅ 增强路由: Layout 去重、增量预取、智能缓存失效
- ✅ 新缓存 API: revalidateTag 需要 cacheLife,新增 updateTag 实现即时更新
- ✅ 异步 API: params、searchParams、cookies、headers 必须 await
- ✅ Turbopack: 默认启用,开发和生产环境性能大幅提升
- ✅ DevTools MCP: AI 辅助调试,统一日志查看
面试准备建议:
- 重点掌握 Cache Components: "use cache" 指令、cacheLife 配置、与 PPR 的关系
- 理解 proxy.ts: 与 middleware.ts 的区别、迁移方法、使用场景
- 熟悉新缓存 API: revalidateTag vs updateTag、SWR 行为、read-your-writes 语义
- 掌握异步 API: await params、await cookies、为什么要改成异步
- 了解性能提升: Turbopack 优势、路由增强、预取优化
- 迁移知识: 废弃功能列表、如何升级现有项目
常见面试问题:
- Cache Components 解决了什么问题?
- proxy.ts 与 middleware.ts 的核心区别是什么?
- revalidateTag 和 updateTag 分别适用什么场景?
- 为什么 params 要改成异步的?
- Next.js 16 的默认行为与之前版本有什么不同?