利用 Next.js 16.4 优化 v0 的导航体验

利用 Next.js 16.4 优化 v0 的导航体验

💡 原文英文,约2200词,阅读约需8分钟。
📝

内容提要

Next.js 16.4 推出部分预取与缓存组件,Vercel 用新 Skill 优化 v0 导航:预取可复用内容,返回时不再显示骨架屏。通过 instant() 测试驱动重构,按需预取并控制成本,最终整体日成本降 2.9%,请求成本降 14.4%。

🔎

延伸解读

部分预取如何改变导航体验

Next.js 16.4 的部分预取允许预取路由中可复用的部分,使内容在点击前就绪,其余部分在导航时加载。缓存内容(包括浏览器中的个性化内容)可被预取并在返回时复用。对于动态路由,prefetch={true} 会解析目标 URL,从而将缓存内容包含在预取中。这减少了骨架屏,使页面更像单页应用。

用 instant() 测试驱动重构

instant() Playwright 助手充当点击的锁,它阻止仅在导航期间到达的内容,让测试可以断言点击前可用的 UI。在示例中,测试检查模板详情页的头部、标题和预览是否已可用,且不显示加载骨架。如果任何 UI 缺失,测试失败。这为重构提供了明确、可测试的目标。

优化循环与成本控制

Next.js 16.4 包含 next-partial-prefetching-optimizer Skill,引导代理循环:选择导航和目标 UI、编写 instant() 测试、添加断言并确认失败、重构应用、重新构建并运行测试,直到通过。在 v0 中,代理将工作拆分为功能特定的 PR,并跟踪运行时缓存支出、请求量、活动 CPU 和数据传输。通过调整预取时机(如悬停时预取)和使用 navigation() API 延迟低优先级内容,最终整体日成本降低 2.9%,请求成本降低 14.4%。

个性化内容的缓存策略

对于个性化内容,如 /settings 下的页面,使用 'use cache: private' 将内容保留在浏览器中。通过将 scope 传入缓存函数,使其成为缓存键的一部分,隔离不同账户或团队的内容。对于用户可更改的数据,将缓存读取与更新操作配对,例如使用 updateTag() 在保存后立即过期缓存,确保用户在同一往返中看到新值。

❓

Q&A

Next.js 16.4 的部分预取(Partial Prefetching)是什么?它和之前的预取方式有什么不同?

部分预取是 Next.js 16.3 引入、16.4 增强的功能。之前的 prefetch={true} 会获取整个目标页面(包括动态内容),可能预取用户从未打开的页面,且点击时若预取未完成仍会慢。部分预取只预取路由中可复用的部分,使其在点击前就绪,其余部分在导航时加载。缓存内容(包括浏览器中存储的个性化内容)可被预取并在返回时复用。对于选定的链接,prefetch={true} 会解析目标 URL,从而将动态路由(如 /products/[id])的缓存内容纳入预取。

如何用 instant() 测试来驱动导航优化?

instant() 是 Playwright 测试辅助函数,它像锁一样控制点击:保留仅在导航期间到达的内容,让测试可以断言点击前已可用的 UI。例如,测试点击模板卡片后,断言模板详情骨架屏不存在,且头部、标题、预览可见。如果缺少这些 UI,测试失败。这个测试作为可测试目标,指导重构,并保留在代码库中用于 CI 回归检查。

next-partial-prefetching-optimizer Skill 的优化循环是怎样的?

该 Skill 引导代理执行以下循环,直到 instant() 测试通过:1. 选择导航和目标 UI;2. 为当前 UI 编写通过的 instant() 测试;3. 添加目标断言并确认失败;4. 使用缓存和预取重构应用;5. 重新构建并重新运行测试;6. 若仍失败则返回步骤 4,若通过则验证回归检查并保留测试。这个循环在大型代码库中特别有用,代理可以持续迭代直到测试通过。

在 v0 的 Templates 页面中,如何避免不必要的预取以控制成本?

最初,所有可见卡片都触发了更丰富的预取,导致对 /templates/[id] 的预取请求增加了 2.9 倍。为了控制成本,改为在用户悬停、聚焦或触摸卡片时才启动更丰富的预取,而不是卡片进入视口时。实现方式:使用状态 intent,在 onMouseEnter、onFocus、onTouchStart 时设为 true,然后 Link 的 prefetch 属性根据 intent 决定是 true 还是 'auto'。这样只预取用户可能打开的卡片,而不把每张卡片视为同等可能。

在 v0 的 Settings 页面中,如何处理个性化内容的缓存和预取?

Settings 页面内容是个性化的,需要保留在浏览器中,因此使用 'use cache: private'。URL 中包含账户或团队标识(如 /acme/settings/advanced),将 scope 传入缓存函数使其成为缓存键的一部分,隔离不同账户或团队的内容。Advanced 页面可复用结果 5 分钟(cacheLife({ stale: 300 }))。对于用户可更改的数据,读取时配对更新操作,例如保存自定义指令时调用 updateTag() 立即过期缓存。然后为 Advanced、Workspace 和 Usage 链接添加 prefetch={true},解析每个作用域目标并包含其缓存内容。其他页面在点击后加载,返回时保持可用。

Next.js 16.4 的 navigation() API 有什么作用?在 v0 中如何应用?

navigation() API 允许决定哪些缓存工作等到导航时进行。在缓存工作前调用 navigation(),这些工作会等到页面打开后才执行,然后保持可用以供返回访问。这适用于不需要立即加载的低优先级内容。在 v0 中,用于 /projects/[id] 页面的 Recent Chats 部分:项目详情被缓存和预取,而 Recent Chats 在 navigation() 之后加载,因此不会预取,但返回时仍可用。也用于 /settings/usage,保持标签、过滤器和 Activity UI 在预取中,而积分仪表盘和使用字段在导航时加载。

这次优化后,v0 的整体成本和性能指标有什么变化?

所有 PR 合并后,整个应用比较显示:总日成本降低了 2.9%,尽管共享运行时缓存支出增长到重构前的 4.3 倍(每天增加约 3.44 美元)。请求量减少、计算和传输降低抵消了增加,使跟踪的请求服务成本下降了 14.4%。同时,覆盖这些导航的 instant() 测试数量翻倍。用户打开页面时看到更多有用内容,返回时加载状态更少。由于同期流量和无关部署变化,整体数字被视为确认重构未使 v0 更昂贵,而非仅由本次工作带来的节省。

🏷️

标签

➡️

继续阅读