nextjs学习8:静态渲染、动态渲染、Streaming渲染

现在让我们新建一个 app/cats/page.js,代码如下:

js 复制代码
export default async function Page() {
  const url = (await (await fetch('https://api.thecatapi.com/v1/images/search')).json())[0].url
  
  return (
    <img src={url} width="300" alt="cat" />
  )
}

其中,api.thecatapi.com/v1/images/s... 是一个返回猫猫图片的接口,每次调用都会返回一张随机的猫猫图片数据。

现在让我们运行 npm run dev,开发模式下,每次点清空缓存强制刷新时都会返回一张新的图片。

现在让我们运行 npm run build && npm run start,然而此时每次点清空缓存强制刷新时都还是同一张图片。

这是为什么呢?

让我们看下构建时的输出结果:

/cats 被标记为 Static,表示被预渲染为静态内容。也就是说,/server的返回内容其实在构建的时候就已经决定了。页面返回的图片正是构建时调用猫猫接口返回的那张图片。

那么问题来了,如何让 /server 每次都返回新的图片呢?

这就要说到 Next.js 的服务端渲染策略了。

服务端渲染策略

Next.js 存在三种不同的服务端渲染策略:

  • 静态渲染
  • 动态渲染
  • Streaming

静态渲染(Static Rendering)

这是默认渲染策略路由在构建时渲染,或者在重新验证后后台渲染,其结果会被缓存并且可以推送到 CDN。

适用于未针对用户个性化且数据已知的情况,比如静态博客文章、产品介绍页面等。

开头中的例子就是构建时渲染。那么如何在重新验证后后台渲染呢?

这里说一种方法:使用路由段配置项 revalidate

修改 app/cats/page.js,代码如下:

js 复制代码
export const revalidate = 10

export default async function Page() {

  const url = (await (await fetch('https://api.thecatapi.com/v1/images/search')).json())[0].url
  
  return (
    <img src={url} width="300" alt="cat" />
  )
}

此时虽然在 npm run build的输出中,/cats依然是标记为静态渲染,但图片已经可以更新了,虽然每隔一段时间才更新。

其中 revalidate=10表示设置重新验证频率为 10s,但是要注意:

这句代码的效果并不是设置服务器每 10s 会自动更新一次 /cats。而是至少 10s 后进行重新验证。

举个例子,假设你现在访问了 /cats,此时时间设为 0s,10s 内持续访问,/cats 返回的都是之前缓存的结果。当 10s 过后,假设你第 12s 又访问了一次 /cats,此时虽然超过了 10s,但依然会返回之前缓存的结果,但同时会触发服务器更新缓存,当你第 13s 再次访问的时候,就是更新后的结果。

简单来说,超过 revalidate 设置时间的首次访问会触发缓存更新,如果更新成功,后续的返回就都是新的内容,直到下一次触发缓存更新。

动态渲染(Dynamic Rendering)

路由在请求时渲染,适用于针对用户个性化或依赖请求中的信息(如 cookie、URL 参数)的情况。

在渲染过程中,如果使用了动态函数(Dynamic functions)或者未缓存的数据请求(uncached data request),Next.js 就会切换为动态渲染

使用动态函数(Dynamic functions)

在 Next.js 中这些动态函数是:

使用这些函数的任意一个,都会导致路由转为动态渲染。

第一个例子,修改 app/cats/page.js,代码如下:

js 复制代码
import { cookies } from 'next/headers'

export default async function Page() {
  const cookieStore = cookies()
  const theme = cookieStore.get('theme')

  const url = (
    await (await fetch('https://api.thecatapi.com/v1/images/search')).json()
  )[0].url

  return <img src={url} width="300" alt="cat" />
}

运行 npm run build && npm run start,此时 /cats显示为动态渲染:

现在每次访问,都能访问到不同的图片。

第二个例子,使用 searchParams,修改 app/cats/page.js,代码如下:

js 复制代码
export default async function Page({ searchParams }) {
  const url = (await (await fetch('https://api.thecatapi.com/v1/images/search')).json())[0].url
  return (
    <>
      <img src={url} width="300" alt="cat" />
      {new Date().toLocaleTimeString()}
      {JSON.stringify(searchParams)}
    </>
  )
}

运行 npm run build && npm run start,此时 /cats动态渲染。

但是图片在页面刷新的时候并没有改变

页面确实是动态渲染,因为每次刷新时间都发生了改变

但为什么图片没有更新呢?

这是因为动态渲染和数据请求缓存是两件事情页面动态渲染并不代表页面涉及的请求一定不被缓存

正是因为 fetch 接口的返回数据被缓存了,这才导致了图片每次都是这一张。

现在再来看看使用了非缓存的数据请求的情况,修改 app/cats/page.js,代码如下:

js 复制代码
export default async function Page({ searchParams }) {
  const url = (await (await fetch('https://api.thecatapi.com/v1/images/search', { cache: 'no-store' })).json())[0].url
  
  console.log('是否每次都会执行')
  return (
    <>
      <img src={url} width="300" alt="cat" />
      {new Date().toLocaleTimeString()}
      {JSON.stringify(searchParams)}
    </>
  )
}

我们为 fetch 请求添加了 { cache: 'no-store' },使 fetch 请求退出了缓存。此时运行生产版本,图片和时间在刷新的时候都会改变。

可以看到,在命令行每次刷新页面都会打印console.log('是否每次都会执行')

同样是转为动态渲染,为什么使用 cookies 的时候,fetch 请求没有被缓存呢?

当你在 headerscookies 方法之后使用 fetch 请求会导致请求退出缓存,这是 Next.js 的自动逻辑。

当使用 searchParams 并没有使fetch请求退出缓存,为了退出缓存,所以要加{ cache: 'no-store' }

但还有哪些情况导致 fetch 请求自动退出缓存呢?

使用未缓存的数据请求(uncached data request)

在 Next.js 中,fetch 请求的结果默认会被缓存,但你可以设置退出缓存,一旦你设置了退出缓存,就意味着使用了未缓存的数据请求(uncached data request),会导致路由进入动态渲染,如:

  • fetch 请求添加了 cache: 'no-store'选项
  • fetch 请求在路由处理程序中并使用了 POST 方法
  • headerscookies 方法之后 使用 fetch请求
  • 配置了路由段选项 const dynamic = 'force-dynamic'

修改 app/cats/page.js,代码如下:

js 复制代码
export default async function Page() {
  const url = (await (await fetch('https://api.thecatapi.com/v1/images/search', { cache: 'no-store' })).json())[0].url
  return (
    <>
      <img src={url} width="300" alt="cat" />
      {new Date().toLocaleTimeString()}
    </>
  )
}

此时页面会转为动态渲染,每次刷新页面都会出现新的图片。

关于动态渲染再重申一遍:数据缓存和渲染策略是分开的。假如你选择了动态渲染,Next.js 会在请求的时候再渲染 RSC Payload 和 HTML,但其中涉及的数据请求,依然是可以从缓存中获取的。

Streaming 渲染

Streaming 渲染是将页面的 HTML 拆分成多个 chunks,然后逐步将这些块从服务端发送到客户端。

这样就可以更快的展现出页面的某些内容,而无需在渲染 UI 之前等待加载所有数据。提前发送的组件可以提前开始水合,这样当其他部分还在加载的时候,用户可以和已完成水合的组件进行交互,有效改善用户体验。

在 Next.js 中有两种实现 Streaming 的方法:

  1. 页面级别,使用 loading.jsx
  2. 特定组件,使用 <Suspense>

总结

静态渲染是指:在构建阶段(npm run build) ,Next.js 就把页面转换成 HTML、CSS、JS 文件,部署后这些文件直接存在服务器 / CDN 上。用户访问时,服务器不用再计算,直接把现成的 HTML 返回给用户。

Next.js 14+(App Router)中,默认就是静态渲染,满足以下条件:

  • 页面 / 组件中没有使用动态函数(比如 headers()cookies()useSearchParams() 等);
  • 数据请求用 fetch 且没有配置cache: 'no-store'
  • 没有显式声明 export const dynamic = 'force-dynamic'
js 复制代码
// app/page.tsx(首页,静态渲染)
// 数据请求:fetch 默认静态(构建时获取)
async function getPosts() {
  // fetch 无特殊配置,构建时请求一次,结果固化到 HTML 中
  const res = await fetch('https://api.example.com/posts');
  return res.json();
}

动态渲染是指:不在构建阶段生成 HTML,而是在用户每次请求时(运行时),服务器实时计算、生成 HTML 并返回。

  • 使用动态函数:headers()cookies()useSearchParams()redirect()notFound() 等;
  • 数据请求配置 cache: 'no-store'
  • 显式声明 export const dynamic = 'force-dynamic'(App Router);
js 复制代码
// app/user/page.tsx(用户页,动态渲染)
import { cookies } from 'next/headers';

// 显式声明动态渲染(可选,用了动态函数会自动触发)
export const dynamic = 'force-dynamic';

async function getUserInfo() {
  // 读取 cookies(动态函数),每次请求都要读
  const cookieStore = cookies();
  const userId = cookieStore.get('userId')?.value;
  
  // 每次请求都实时获取用户数据
  const res = await fetch(`https://api.example.com/user/${userId}`, {
    cache: 'no-store', // 禁用缓存,强制实时请求
  });
  return res.json();
}

注意:渲染的方式和 数据的缓存是两个独立的开关,互不绑定

动态渲染决定的是什么时候把数据组装成页面(RSC Payload/HTML) ,请求时实时组装;

数据缓存决定的是组装页面时,数据是从缓存拿,还是实时请求拿(可选缓存)。

两者的关系:动态渲染是组装页面的时机,数据缓存是组装页面的原料来源

  • 动态渲染 = 动态出餐:顾客点单后(用户请求),后厨才开始把食材组装成套餐(生成 RSC Payload/HTML),而不是提前做好套餐(静态渲染);
  • 数据缓存 = 食材缓存:后厨组装套餐时,食材可以是提前备好在冰箱的缓存食材(数据缓存),也可以是现去菜市场买的新鲜食材(实时请求);
相关推荐
小满zs5 天前
Next.js第二十章(MDX)
前端·next.js
小p6 天前
nextjs学习6:服务端组件和客户端组件
next.js
多啦C梦a7 天前
《双Token机制?》Next.js 双 Token 登录与无感刷新实战教程
前端·全栈·next.js
小p7 天前
nextjs学习4:创建服务端 API(路由处理程序)
next.js
小p7 天前
nextjs学习5:Suspense 与 Streaming
next.js
无敌暴龙战士通关前端8 天前
3天速成 使用AI《从零开发一款 AI 面试作弊助手》一
react.js·next.js
小满zs10 天前
Next.js第十九章(服务器函数)
前端·next.js
小p11 天前
nextjs学习2:app router
next.js