next-cache-components-optimizer

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

next-cache-components-optimizer

next-cache-components-optimizer

Two loops, shared levers and primitives, different diagnostics:
  • Page-render loop (ppr-loop.md) — grow the static shell of a single page. Rank Suspense fallback areas on a shell-only render.
  • Nav loop (instant-nav-loop.md) — when the user clicks a link from A to B, show B's static layout immediately (chrome, structure, content-shaped fallbacks) instead of holding A's UI until B's data resolves. Capture B's suspended boundaries post-
    pushstate
    , classify each by
    suspended_by[].name
    , drop SSR-only client hooks.
Pick one and run it end-to-end.
两个循环,共享控制手段和基础组件,诊断方式不同:
  • 页面渲染循环ppr-loop.md)——扩展单个页面的静态外壳。在仅渲染外壳的模式下对Suspense回退区域进行排序。
  • 导航循环instant-nav-loop.md)——当用户从页面A点击链接跳转到页面B时,立即显示页面B的静态布局(框架、结构、内容化回退组件),而非在B的数据解析完成前一直显示A的UI。在
    pushstate
    之后捕获B的挂起边界,按
    suspended_by[].name
    对每个边界进行分类,移除仅SSR的客户端钩子。
选择其中一个循环并端到端运行。

requires

前置要求

  • next-dev-loop
    initiated for this session — it opens the headed browser, exposes the
    agent-browser
    CLI, and wires the dev MCP server that provides
    mcp get_logs
    .
  • cacheComponents: true
    in
    next.config.ts
    . Refuse otherwise.
  • 本次会话已启动
    next-dev-loop
    ——它会打开带界面的浏览器,暴露
    agent-browser
    CLI,并连接提供
    mcp get_logs
    功能的开发MCP服务器。
  • next.config.ts
    中已设置
    cacheComponents: true
    。否则将拒绝执行。

preflight (shared)

预检(共享步骤)

  1. Confirm
    cacheComponents: true
    .
  2. The user must already be at the page each loop needs in the headed browser (from
    next-dev-loop
    ) — logged in, with any state set up. This skill can't drive auth, SSO, or MFA; it takes the manual setup as the starting point. (Each sub-loop names which page it expects.)
  3. agent-browser get url
    to anchor the current route.
Each loop sets the instant cookie as needed (see the shared
instant cookie
section below).
  1. 确认已设置
    cacheComponents: true
  2. 用户必须已在带界面的浏览器(来自
    next-dev-loop
    )中打开每个循环所需的页面
    ——已登录,且所有状态已配置完成。本技能无法处理认证、SSO或MFA;需以手动配置的状态作为起始点。(每个子循环会说明它预期的页面。)
  3. 执行
    agent-browser get url
    来锚定当前路由。
每个循环会根据需要设置即时Cookie(见下方的「即时Cookie」共享部分)。

instant cookie (shared)

即时Cookie(共享组件)

Both loops use the
next-instant-navigation-testing
cookie to freeze the framework's dynamic-data writes. Once set, visible content on the page is the static shell + Suspense fallbacks — that's what we capture to assess the optimization.
Set it with a pending-lock tuple
[0, "<unique-id>"]
. The id is any unique string; the convention is a
p
-prefixed random stamp so concurrent scopes don't collide:
agent-browser cookies set next-instant-navigation-testing '[0,"p<random>"]' \
  --url <origin>
Each loop's preflight specifies when to set it within the flow. Clear it at the end (see
teardown
below).
两个循环均使用
next-instant-navigation-testing
Cookie来冻结框架的动态数据写入。设置后,页面上的可见内容即为静态外壳 + Suspense回退组件——这正是我们捕获用于评估优化效果的内容。
使用待锁定元组
[0, "<unique-id>"]
进行设置。ID可以是任意唯一字符串;惯例是使用以
p
开头的随机戳,避免并发作用域冲突:
agent-browser cookies set next-instant-navigation-testing '[0,"p<random>"]' \
  --url <origin>
每个循环的预检步骤会指定在流程中的何时设置该Cookie。在结束时清除它(见下方的清理步骤)。

decide which loop

选择合适的循环

  • Page-render when the complaint is about one route's initial load. Read ppr-loop.md.
  • Nav when it's about navigating between two routes. Read instant-nav-loop.md.
Ambiguous → ask.
  • 当问题是关于某一路由的初始加载时,选择页面渲染循环。阅读ppr-loop.md
  • 当问题是关于两个路由间的导航时,选择导航循环。阅读instant-nav-loop.md
若情况不明确,请询问用户。

shared refactor levers

共享重构手段

  • Push down — extract I/O into a Suspense-wrapped child so the parent stays static and static siblings lift into the shell.
    • Recurse, don't blind-wrap. If a Suspense boundary already wraps a component containing both static content and the I/O, read inside, extract the I/O-dependent JSX into a new leaf, and lift the static siblings up.
  • Cache
    'use cache'
    +
    cacheLife(<profile>)
    . Always ask the user for freshness; map to a preset (
    seconds
    /
    minutes
    /
    hours
    /
    days
    /
    weeks
    /
    max
    /
    default
    ).
Push-down and cache compose: push-down lifts static structure, cache eliminates the remaining data gap.
  • 下移提取——将I/O操作提取到由Suspense包裹的子组件中,使父组件保持静态,静态兄弟组件被提升到外壳中。
    • 递归处理,而非盲目包裹。如果某个Suspense边界已包裹了同时包含静态内容和I/O操作的组件,请深入内部,将依赖I/O的JSX提取到新的叶子组件中,并将静态兄弟组件提升上去。
  • 缓存配置——
    'use cache'
    +
    cacheLife(<profile>)
    。始终询问用户新鲜度要求;映射到预设值(
    seconds
    /
    minutes
    /
    hours
    /
    days
    /
    weeks
    /
    max
    /
    default
    )。
下移提取和缓存配置可结合使用:下移提取提升静态结构,缓存配置消除剩余的数据缺口。

propose via plan mode (shared)

通过计划模式提出方案(共享步骤)

Each refactor goes through plan mode before applying. Treat this as a signal: the application work is non-trivial agentic engineering, not a templated edit. This skill provides the framework — which lever to reach for, which candidate to fix, what the expected visible delta is — but the real work (which file to edit, how to cleanly extract the I/O, where to place the new Suspense boundary, which
cacheLife
profile to ask the user for) is a judgment call you have to think through. Plan mode forces a coherent proposal before touching code, and gives the user a chance to redirect on any of those decisions.
每次重构在应用前都需经过计划模式。将此视为一个信号:应用开发工作是具有重要意义的智能代理工程,而非模板化编辑。本技能提供框架——使用哪种手段、修复哪个候选对象、预期的可见变化是什么——但实际工作(编辑哪个文件、如何干净地提取I/O操作、新的Suspense边界放置在哪里、询问用户选择哪个
cacheLife
配置文件)需要你做出判断。计划模式要求在修改代码前提出连贯的方案,并让用户有机会对任何决策进行调整。

no-shell bailout (shared)

无外壳时终止操作(共享步骤)

The levers presume a shell exists to grow or cache toward. If the route is fully blocking — HTTP 500 with
blocking-route
or
NEXT_STATIC_GEN_BAILOUT
in
mcp get_logs
, or zero Suspense boundaries on a visibly-rendered page — there's no shell. Surface the structural blocker and stop; the user has to wrap the offending dynamic access in
<Suspense>
before either loop can help.
这些手段假定存在可扩展或缓存的外壳。如果路由完全阻塞——
mcp get_logs
中显示HTTP 500且包含
blocking-route
NEXT_STATIC_GEN_BAILOUT
,或者可见渲染的页面上没有任何Suspense边界——则不存在外壳。指出结构性阻塞并终止操作;用户必须先将有问题的动态访问包裹在
<Suspense>
中,两个循环才能提供帮助。

verify requires a visible delta (shared)

验证需要可见的变化(共享步骤)

Each loop captures a baseline screenshot of the shell before applying any change, then re-screenshots after. Report both paths in the final summary so the user can see what changed. The two captures must visibly differ — fallback area shrunk, content promoted to the static surface, target fallback gone or content-shaped. Identical-looking captures mean the refactor didn't land; undo. "Compiles cleanly" is not the bar.
Hide the dev overlay before each screenshot. The Next.js dev overlay (
<nextjs-portal>
at the document root) renders instant-nav guidance, build errors, and other dev chrome that pollute the before/after comparison. Hide it, screenshot, restore:
agent-browser eval "document.querySelector('nextjs-portal').style.display='none'"
agent-browser screenshot <path>
agent-browser eval "document.querySelector('nextjs-portal').style.display=''"
每个循环在应用任何更改前都会捕获外壳的基准截图,然后在更改后重新截图。在最终总结中展示这两个截图路径,以便用户查看变化。两张截图必须有明显差异——回退区域缩小、内容被提升到静态表面、目标回退组件消失或变为内容化样式。如果截图看起来完全相同,则说明重构未生效;请撤销更改。“编译通过”不是判断标准。
每次截图前隐藏开发覆盖层。Next.js开发覆盖层(位于文档根目录的
<nextjs-portal>
)会渲染即时导航指引、构建错误和其他开发界面元素,会干扰前后对比。先隐藏它,截图,然后恢复:
agent-browser eval "document.querySelector('nextjs-portal').style.display='none'"
agent-browser screenshot <path>
agent-browser eval "document.querySelector('nextjs-portal').style.display=''"

anti-patterns (shared)

反模式(共享注意事项)

Don't replace granular Suspense boundaries with a top-level loading skeleton. A
loading.tsx
for the whole segment, or a root-level
<Suspense fallback={<Skeleton />}>
(or worse,
fallback={null}
that blanks the UI), defeats this skill's optimization — which is to extract real static chrome above each granular boundary and use content-shaped fallbacks per region. A coarse "the page is loading" stand-in bypasses the work entirely.
不要用顶层加载骨架替换细粒度的Suspense边界。为整个分段设置
loading.tsx
,或在根级别使用
<Suspense fallback={<Skeleton />}>
(更糟的是使用
fallback={null}
使UI空白),会破坏本技能的优化——本技能的目的是提取每个细粒度边界上方的真实静态框架,并为每个区域使用内容化回退组件。粗糙的“页面正在加载”占位符会完全跳过优化工作。

gotchas (shared)

注意事项(共享要点)

  • Dev doesn't prefetch the way production does, and routes compile on first hit — so after a navigation or reload, the DOM keeps updating for noticeably longer than the eventual production experience. Wait patiently for the DOM to stabilize before capturing the React tree or taking a screenshot — e.g., poll
    document.documentElement.innerHTML.length
    until it's unchanged across two consecutive reads. A fixed short delay risks sampling mid-render.
  • Don't try to verify nav prefetch by inspecting dev network traffic — dev doesn't fire prefetch requests at all, so the network tab, manual
    router.prefetch()
    calls, and
    <Link prefetch={true}>
    will all look broken regardless of whether your code is correct. The cookie-locked SPA-nav recipe in instant-nav-loop.md under
    verify
    is already the canonical recipe for this — it simulates what production would prerender into the prefetched RSC without requiring prefetch to actually fire. Use it; don't invent a network-tab alternative.
  • The diagnose pipeline can be flaky — DevTools attachment timing, DOM-settle races, and dev compilation effects can each produce inconsistent captures from one run to the next. When a result feels off (a candidate appears that you don't expect, or one you expect doesn't), re-run the diagnose 2–3 times and cross-check; boundaries that appear consistently are real, one-off appearances are noise.
  • 开发环境不会像生产环境那样预获取资源,且路由在首次访问时才会编译——因此在导航或重新加载后,DOM更新的时间会明显长于最终的生产环境体验。在捕获React树或截图前,耐心等待DOM稳定——例如,轮询
    document.documentElement.innerHTML.length
    直到连续两次读取结果不变。固定的短延迟可能会在渲染过程中进行采样。
  • 不要尝试通过检查开发环境的网络流量来验证导航预获取——开发环境根本不会触发预获取请求,因此无论代码是否正确,网络标签页、手动调用
    router.prefetch()
    以及
    <Link prefetch={true}>
    都会看起来像是失效的。instant-nav-loop.md
    verify
    部分的Cookie锁定SPA导航方案已经是验证这一点的标准方法——它模拟了生产环境中会预渲染到预获取RSC中的内容,而无需实际触发预获取。请使用该方法;不要自行发明基于网络标签页的替代方案。
  • 诊断流程可能不稳定——DevTools附加时机、DOM稳定竞争条件和开发编译效果都可能导致每次运行的捕获结果不一致。当结果看起来异常(出现了你不期望的候选对象,或者你期望的候选对象未出现),重新运行诊断2-3次并交叉检查;持续出现的边界是真实的,单次出现的是干扰信息。

reference (shared primitives)

参考(共享基础组件)

agent-browser react suspense          add --only-dynamic to filter
--json                                server-side to actually-
                                      suspended boundaries. Each
                                      entry has jsx_source +
                                      suspended_by[] with raw blocker
                                      names (usePathname, cookies,
                                      fetch, cache, ...); classify by
                                      name for per-loop rules

POST /__nextjs_original-stack-frames  body { frames: StackFrame[],
                                      isServer, isEdgeServer,
                                      isAppDirectory }; returns one
                                      result per frame with
                                      file:line:column

mcp get_logs                          dev MCP tool from
                                      next-dev-loop; surfaces
                                      blocking-route /
                                      NEXT_STATIC_GEN_BAILOUT 500s

cacheLife('<profile>')                default | seconds | minutes
                                      | hours | days | weeks | max
Per-loop primitives in instant-nav-loop.md.
agent-browser react suspense          添加--only-dynamic进行过滤
--json                                服务器端实际挂起的边界。每个
                                      条目包含jsx_source +
                                      suspended_by[],其中包含原始阻塞
                                      器名称(usePathname、cookies、
                                      fetch、cache等);按名称分类以符合
                                      每个循环的规则

POST /__nextjs_original-stack-frames  请求体{ frames: StackFrame[],
                                      isServer, isEdgeServer,
                                      isAppDirectory };返回每个栈帧对应的
                                      file:line:column结果

mcp get_logs                          来自next-dev-loop的开发MCP工具;显示
                                      blocking-route /
                                      NEXT_STATIC_GEN_BAILOUT 500错误

cacheLife('<profile>')                default | seconds | minutes
                                      | hours | days | weeks | max
每个循环的基础组件见instant-nav-loop.md

teardown (shared)

清理步骤(共享步骤)

Delete the cookie by name — overwrite with an expired stamp:
agent-browser cookies set next-instant-navigation-testing x \
  --url <origin> --expires 1
Never
agent-browser cookies clear
(no args) — wipes auth.

Sibling of
next-dev-loop
— initiate that first.
通过名称删除Cookie——用过期时间戳覆盖:
agent-browser cookies set next-instant-navigation-testing x \
  --url <origin> --expires 1
切勿执行
agent-browser cookies clear
(无参数)——这会清除认证信息。

next-dev-loop
的配套技能——请先启动
next-dev-loop