Next.js 15+ 异步请求 API、默认缓存策略反转与 PPR(部分预渲染)架构是如何工作的?

Next.js 15 是 App Router 架构走向成熟的分水岭版本,它彻底重构了 请求上下文 API 的同步/异步契约、默认缓存(Caching)策略,并引入了 PPR(Partial Prerendering,部分预渲染) 渲染模型。

一、 为什么 Next.js 15 将 cookies()、headers()、params 和 searchParams 全部改为异步(Async)?

在 Next.js 13/14 中,cookies()、headers() 和路由 params 是同步读取的,这迫使服务端必须等整个 HTTP 请求头与上下文解析完毕才能开始准备渲染。改为**返回 Promise(必须 await)**后:

  1. 为 PPR 部分预渲染铺平道路:只要组件还没执行到 await cookies() 这一行,所有前置的静态组件外壳可以在构建期或请求到达的 0ms 瞬间直接从 CDN 流式发出!
  2. 明确的动态渲染边界:一旦代码在 <Suspense> 内部执行了 await cookies() 或 await headers(),Next.js 就精确地只把该 <Suspense> 子树标记为动态流式空洞(Dynamic Hole),而不会连累整页退化为动态 SSR。

二、 Next.js 14 vs Next.js 15 默认缓存策略反转对比表

缓存场景 Next.js 14 默认行为(激进缓存,易出陈旧数据坑) Next.js 15+ 默认行为(显式Opt-in缓存,符合直觉)
fetch() 请求 默认 cache: 'force-cache'(永久缓存) 默认 no-store(每次请求拉取最新数据);需缓存时显式传 cache: 'force-cache' 或 next: { revalidate: N }
GET Route Handlers (app/api/**/route.ts) 默认被当作静态文件在构建期缓存! 默认 dynamic(每次动态执行);需静态化时显式声明 export const dynamic = 'force-static'
Client Router Cache(客户端路由缓存) 页面段默认在浏览器内存缓存 30 秒 页面段默认 staleTime: 0(导航前进时始终获取最新服务端数据,仅浏览器后退/前进复用缓存)

三、 Next.js 15 异步 API 与 use cache / PPR 代码实战

import { Suspense } from 'react'
import { cookies } from 'next/headers'

// Next.js 15+:params 与 searchParams 均为 Promise,必须 await 解包
export default async function OrderPage({
  params,
  searchParams,
}: {
  params: Promise<{ id: string }>
  searchParams: Promise<{ tab?: string }>
}) {
  const { id } = await params
  const { tab = 'detail' } = await searchParams

  return (
    <section>
      {/* 静态预渲染头部:构建期直接固化进 HTML 外壳 */}
      <h1>订单详情 #{id}(当前页签:{tab})</h1>

      {/* 动态流式区域:仅此 Suspense 内部读取 Cookie,不阻塞外层静态壳秒开 */}
      <Suspense fallback={<p>正在校验用户专属优惠...</p>}>
        <UserDiscountBanner orderId={id} />
      </Suspense>
    </section>
  )
}

async function UserDiscountBanner({ orderId }: { orderId: string }) {
  const cookieStore = await cookies()
  const sessionToken = cookieStore.get('session_id')?.value
  const discount = await fetchUserDiscount(orderId, sessionToken)
  return <div className="banner">专属抵扣:¥{discount}</div>
}

同分类其他题目