Skip to content

Next.js 16 面试题汇总

聚焦 Next.js 16 最新特性:Cache Components、proxy.ts、增强路由、新缓存 API,排除已废弃的 middleware.ts 和 Pages Router 过时用法

目录


基础概念

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,实现细粒度缓存控制

代码示例:

tsx
// 启用 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,更可预测

代码示例:

ts
// 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*']
}

迁移步骤:

  1. 重命名 middleware.tsproxy.ts
  2. 重命名导出函数为 proxy
  3. 逻辑保持不变

追问: 什么场景下还需要使用 middleware.ts?

4. Next.js 16 的默认行为有哪些变化?

答案要点:

  • 默认动态渲染: 所有页面默认在请求时执行,不再隐式缓存
  • Turbopack 默认启用: next devnext build 默认使用 Turbopack
  • params/searchParams 必须 await: 路由参数和查询参数现在是 Promise
  • cookies/headers/draftMode 必须 await: 这些 API 现在返回 Promise
  • revalidateTag 需要 cacheLife: 必须提供第二个参数指定缓存策略
  • 图片质量默认值变化: images.qualities 默认为 [75]

代码示例:

tsx
// ✅ 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

代码示例:

tsx
// 页面级缓存
"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'
  • 支持自定义缓存配置

代码示例:

tsx
// 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
  • 未认证时重定向到登录页
  • 可以修改请求和响应

代码示例:

ts
// 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

代码示例:

ts
// 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 或重新进入视口时优先预取

代码示例:

tsx
// 自动优化的预取
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 预生成静态路径

代码示例:

tsx
// 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': 适合大多数场景

代码示例:

tsx
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 后台刷新

代码示例:

tsx
'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 中定义

代码示例:

tsx
// 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

代码示例:

tsx
// 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' 指令: 文件顶部声明

代码示例:

tsx
// 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

代码示例:

tsx
// 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 加速: 开发和生产环境性能提升

代码示例:

tsx
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 devnext build 默认使用
  • 更快的开发服务器: HMR 速度大幅提升
  • 更快的生产构建: 构建时间显著减少
  • 文件系统缓存: Next.js 16.1 引入,进一步加速

代码示例:

bash
# 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

代码示例:

ts
// 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 配置: 自定义缓存策略

代码示例:

ts
// 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)
  • serverRuntimeConfigpublicRuntimeConfig

已废弃(未来将移除):

  • 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 辅助调试,统一日志查看

面试准备建议:

  1. 重点掌握 Cache Components: "use cache" 指令、cacheLife 配置、与 PPR 的关系
  2. 理解 proxy.ts: 与 middleware.ts 的区别、迁移方法、使用场景
  3. 熟悉新缓存 API: revalidateTag vs updateTag、SWR 行为、read-your-writes 语义
  4. 掌握异步 API: await params、await cookies、为什么要改成异步
  5. 了解性能提升: Turbopack 优势、路由增强、预取优化
  6. 迁移知识: 废弃功能列表、如何升级现有项目

常见面试问题:

  • Cache Components 解决了什么问题?
  • proxy.ts 与 middleware.ts 的核心区别是什么?
  • revalidateTag 和 updateTag 分别适用什么场景?
  • 为什么 params 要改成异步的?
  • Next.js 16 的默认行为与之前版本有什么不同?