spinny:~/writing $ vim nextjs-16-cache-components.md
1~2多年来,Next.js 中最烦人的问题之一一直是:“此页面是静态的还是动态的?”。这似乎是一个简单的问题,直到您添加对 `cookies()` 的调用、具有不同选项的 `fetch`、数据库客户端、CMS、购物车或自定义内容。3~4Next.js 16 很有趣,因为它试图让这个对话变得不那么神秘。它并没有消除复杂性,但它改变了思维模型:默认情况下路由是动态的,缓存在需要时声明自身,并且 `Suspense` 成为用保持新鲜的部分组成快速 shell 的自然方式。5~6要了解的功能是缓存组件。 Stable Turbopack、React Compiler、`proxy.ts` 和新的失效 API 很重要,但它们都围绕着同样的问题:构建快速的应用程序,而不必猜测框架在幕后决定了什么。7~8## 因为这件事很重要9~10在真正的应用程序中,您不仅仅有“静态页面”和“动态页面”。您有不同的需求的不同部分。11~12产品表每天可能会更改几次。价格可能会更频繁地变化。可用性必须几乎是实时的。用户名是个人的。评论可以流式传输。侧边栏可以稳定。购物车没有。13~14如果您将所有事物视为一个整体,那么您总是会陷入两个极端之一:15~16- 积极的缓存和看到旧数据的风险;17- 到处都是动态渲染,性能比必要的要差。18~19缓存组件正是为了避免这种错误的选择。20~21## 实践中的模型22~23通过 `cacheComponents: true`,您可以使用 `"use cache"` 声明可缓存的内容。然后,您可以将持续时间和标签与 `cacheLife()` 和 `cacheTag()` 关联起来。动态部分保持动态,可以使用 `Suspense` 进行隔离。24~25```mermaid26flowchart TD27 Request[用户请求] --> Shell[缓存外壳]28 Request --> Dynamic[动态部分]29 Shell --> FirstPaint[第一个快速内容]30 Dynamic --> Stream[悬念中的流媒体]31 Stream --> Complete[整页]32```33~34设置很小:35~36```typescript37// next.config.ts38import type { NextConfig } from 'next';39~40const nextConfig: NextConfig = {41 cacheComponents: true,42};43~44export default nextConfig;45```46~47最大的变化并不在于配置。这取决于您如何开始编写组件。48~49```tsx50// app/products/[slug]/page.tsx51import { Suspense } from 'react';52import { cacheLife, cacheTag } from 'next/cache';53~54async function getProduct(slug: string) {55 'use cache';56~57 cacheLife('hours');58 cacheTag(`product:${slug}`);59~60 return db.product.findUnique({ where: { slug } });61}62~63async function ProductDetails({ slug }: { slug: string }) {64 const product = await getProduct(slug);65~66 return (67 <section>68 <h1>{product.name}</h1>69 <p>{product.description}</p>70 </section>71 );72}73~74async function LiveInventory({ slug }: { slug: string }) {75 const inventory = await db.inventory.findFirst({ where: { slug } });76 return <p>{inventory.quantity} pezzi disponibili</p>;77}78~79export default async function ProductPage({ params }: { params: Promise<{ slug: string }> }) {80 const { slug } = await params;81~82 return (83 <>84 <ProductDetails slug={slug} />85 <Suspense fallback={<p>Controllo disponibilità...</p>}>86 <LiveInventory slug={slug} />87 </Suspense>88 </>89 );90}91```92~93该页面不必全部缓存或全部动态。产品表可以快速且可重复使用。库存可以保持新鲜。用户可以立即看到某些内容,而无需等待最慢的部分。94~95## `use cache` 是可执行文档96~97我喜欢 `"use cache"` 的一点是它迫使你明确表达意图。当你读到一个函数时,你立即明白有人已经决定:“这个数据可以重用”。98~99当您不使用 `fetch` 时,它特别有用。许多应用程序从 Prisma、Drizzle、内部 SDK、CMS 客户端或服务功能读取数据。在这些情况下,仅基于 `fetch` 选项的旧推理是不够的。100~101经验法则:102~103- 缓存a含量相对稳定;104- 使用细粒度标签;105- 留下动态权限、会话、购物车、通知和事务状态;106- 将慢速部分放入 `Suspense` 中;107- 在说“我们提高了绩效”之前先进行衡量。108~109## 无效而不扔掉所有东西110~111仅当您可以准确更新缓存时,缓存才有用。这里 `cacheTag`、`revalidateTag` 和 `updateTag` 变得很重要。112~113示例:114~115```typescript116'use server';117~118import { updateTag } from 'next/cache';119~120export async function updateProductName(productId: string, name: string) {121 await db.product.update({122 where: { id: productId },123 data: { name },124 });125~126 updateTag(`product:${productId}`);127}128```129~130重要的细节是标签。 `product:${productId}` 给出了精确的边界。 `products` 讲述了一个巨大的桶。起初,巨大的水桶很舒服;几个月后,它就成为您因更改标题而使半个应用程序失效的原因。131~132## Stable Turbopack:您每天听到的部分133~134Next.js 16 将 Turbopack 引入开发和构建中心。这不是最富有诗意的功能,但它是您在工作时感受到的功能:服务器启动更早,刷新更快,构建不再感觉像是被迫喝咖啡休息。135~136也就是说,我不会闭着眼睛迁移充满自定义插件的代码库。我会检查:137~138- 本地构建;139- 非标进口;140- MDX、SVG 和 CSS;141- 剩下的 Webpack 插件;142- 关键页面;143- 构建时间的差异。144~145对于新项目,我会从默认开始。对于成熟的,我会进行谨慎的迁移。146~147## React Compiler:消除噪音,而不是思考148~149React Compiler 1.0 很稳定,Next.js 16 通过 `reactCompiler` 支持它。我们的承诺是减少大量的手动记忆:“为了安全”使用更少的 `memo`、更少的 `useMemo`、更少的 `useCallback`。150~151```typescript152// next.config.ts153import type { NextConfig } from 'next';154~155const nextConfig: NextConfig = {156 reactCompiler: true,157};158~159export default nextConfig;160```161~162我不会把它当作魔法灰尘。当代码很好地遵循 React 规则时,编译器会提供帮助。如果组件有奇怪的副作用、隐藏的突变或使用不当的钩子,则需要首先修复。163~164健康的尝试方法:165~1661. 更新`eslint-plugin-react-hooks`;1672. 纠正实际违规行为;1683. 在受控区域启用它;1694. 测量构建时间和行为;1705. 仅当不再需要时才删除手动记忆。171~172目标不是擦除每个 `useMemo`。我们的目标是停止编写预防性记忆,因为我们害怕渲染。173~174## `proxy.ts` 和网络边界175~176旧的 `middleware.ts` 变为 `proxy.ts`。这是一个名称更改,但它是有道理的:该文件位于请求边界,它不是传统的后端样式通用中间件。177~178```typescript179// proxy.ts180import { NextRequest, NextResponse } from 'next/server';181~182export default function proxy(request: NextRequest) {183 const isLoggedIn = Boolean(request.cookies.get('session'));184~185 if (!isLoggedIn && request.nextUrl.pathname.startsWith('/dashboard')) {186 return NextResponse.redirect(new URL('/login', request.url));187 }188~189 return NextResponse.next();190}191```192~193这里的规则很简单:保持较小。重定向、身份验证路由、标头、必要的重写。如果它开始感觉像是第二个后端,那么它可能做得太多了。194~195## 我将如何真正迁移196~197我不会立即打开所有功能。我会这样做:198~1991. 我更新 Next、React 和 React DOM;2002. 我启动了官方的codemod;2013. 我修复了 `params`、`searchParams`、`cookies()`、`headers()` 和 `draftMode()` 上的重大更改;2024. 我将 `middleware.ts` 迁移到 `proxy.ts`;2035. 我检查构建和关键页面;2046. 我在缓存当前产生摩擦的部分启用缓存组件;2057. 我定义了标签和失效的约定;2068. 我单独尝试React Compiler;2079. 之前和之后的指标比较。208~209好的迁移并不是使用所有新功能的迁移。这使得应用程序的行为更具可读性。210~211## 思维方式发生了哪些变化212~213Next.js 16 最有用的一点是它迫使您更好地命名意图。函数不仅仅是“从数据库获取产品”。它是“获取产品,我可以将其缓存几个小时,我使用此标签使其无效”。组件不仅仅是“渲染页面”。它是“这是快速外壳,这是个人的,这是流媒体的。”214~215起初,这似乎需要更多的工作。然后它就变成了一种平静。性能决策不再隐藏在默认值、启发式和部落记忆的组合中。它们在代码中。216~217## 有用的来源218~219- [Next.js 16 发行说明](https://nextjs.org/blog/next-16)220- [缓存组件 - Next.js 文档](https://nextjs.org/docs/app/getting-started/cache-components)221- [使用缓存 - Next.js 文档](https://nextjs.org/docs/app/api-reference/directives/use-cache)222- [反应编译器 v1.0](https://react.dev/blog/2025/10/07/react-compiler-1)223~
NORMAL · nextjs-16-cache-components.md [readonly]223 lines · :q to close