1实现原理 · 为什么它能做到
先讲清失败的结构性根因:App Router 导航会立刻换掉旧路由内容,而 AnimatePresence 只有在被移除的子元素仍挂载、且包裹层持续存在时才能跑 exit
The reason is structural — in the App Router, when navigating, Next.js unmounts the old route's content and mounts the new one almost immediately.
方案 A 等于 template.tsx(每次导航重挂载)加 pathname 作 key 的 AnimatePresence 加 FrozenRouter(快照 LayoutRouterContext 让旧树继续渲旧内容)
const frozen = useRef(context).current;
方案 B 等于原生 View Transitions API(::view-transition-old/new 动画加 document.startViewTransition 包裹 router.push),或用 next-view-transitions 库开箱接入
::view-transition-old(root) { animation: fade 0.25s both reverse; }
崩溃排查写成有序清单(7 条),覆盖包裹层位置、key、直接 motion 后代、mode="wait"、FrozenRouter、'use client'、单子元素
**Stable, changing key.** The animated child needs `key={pathname}` (or similar) so removal is detected.
验收降级为可独立复现的单文件:用两个 stub 页面加 ?view=/phase= 冻结过渡阶段,先把转场形状从 Next 工程里剥离出来验证
One file is the right tier for validating motion before wiring the router; don't stand up Next just to eyeball a fade.
2核心能力
3外部依赖
| 类型 | 依赖 |
|---|---|
| package | framer-motion(AnimatePresence 与 motion,配合 template.tsx 与 FrozenRouter) |
| package | next-view-transitions(把 View Transitions 接进 App Router 客户端导航) |
| api | Next.js 内部模块 LayoutRouterContext(next/dist/shared/lib/app-router-context.shared-runtime)与 usePathname |
| api | 浏览器原生 View Transitions API(document.startViewTransition、::view-transition-old/new、view-transition-name) |
| network | 交付页从 CDN 取 motion 与 React(产物期引用,不判级;正文未点名具体域) |
| cli | npx / Playwright(宿主侧截图工具)与仓库根 scripts/ 助手(seek-shot.sh、contact-sheet.sh 依赖 ffmpeg) |
4风险提醒 风险提醒:绿色 · 放心使用
- description 的覆盖面大于正文实现(general SPA routing 无具体接线) — 见 second_pass:非 App Router 的 SPA(React Router 等)只有 View Transitions 一条泛化路径,需自行补接线与排查。
- 依赖框架内部模块,升级即可能断裂 — FrozenRouter 走 next/dist/shared/... 私有路径;Next 版本升级后若路径变动,过渡会直接报错(文档已标注,但无版本兼容矩阵或自动探测)。
- mode="wait" 的时延体验风险 — 等待退场完成才挂载新页,慢退场会让导航显得迟钝;文档要求时长约 0.2–0.4s,但没有性能预算或超时跳过退场的兜底。
- 交付验证与真实工程脱节,且 Packaged helper 不匹配(见 second_pass) — stub HTML 能验证曲线形状,但不覆盖 router 上下文、Suspense 与 layout 层级等真实失败面;helper 的秒制时间冻结与本 skill 的状态、相位冻结也不同调。
5第二遍独立确认
- [discrepancy] description 声称的覆盖范围(Next.js App Router 与 general SPA routing)与正文实际覆盖面是否相符 — frontmatter 写 ... for Next.js App Router and general SPA routing.,但正文主体(根因分析、方案 A 的三个文件、7 条排查清单)全部是 App Router 专属;general SPA 只在 View Transitions API 与交付用 stub HTML 两处被顺带覆盖,既无 React Router/Remix 等通用 SPA 路由的接线,也无对应排查清单。属描述略宽于实现(非虚假,但按 description 期望通用方案会落空)。
- [ok] FrozenRouter 依赖的 Next 内部 import 路径真实性 — SKILL.md 给出 import { LayoutRouterContext } from 'next/dist/shared/lib/app-router-context.shared-runtime'; 并在正文、notes、gotchas、references 四处重复提示 internal、跨版本可能变,references 还专列 FrozenRouter import note across versions 一节;即文档自己对该脆弱性做了标注,非隐瞒。
- [ok] View Transitions 验收的可行性(快照是瞬态) — 正文承认 snapshots are transient,并给出替代(截结束态或用 emulate slow animations),验收步骤不依赖捕捉瞬态帧;startViewTransition 的能力守卫在正文与完成前清单第 1 条都要求。
- [discrepancy] Packaged helper 的路径基准与语义 — SKILL.md 的 verify 段仍贴 scripts/seek-shot.sh anim.html 0 1.5 3(时间冻结、仓库根路径),但本 skill 的冻结参数是 ?view= 与 ?phase=(状态与相位),helper 不匹配;scripts/ 也在仓库根而非本 skill 目录。
6结论
083d4f27aaf44464…b6dba3eb75