Conversation
A page that renders a script status must hydrate cleanly for every trigger. The client trigger fails on main: the server renders awaitingLoad and the client renders loading.
A client trigger calls load() during setup, so the status ref starts at loading while the server rendered awaitingLoad. The server now records non-default statuses in the payload. During hydration the status ref reports that value and releases it on app:suspense:resolve. The trigger and loader still run at the same moment.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Checked by hand, since CI cannot show it:
|
commit: |
📦 Package Size📚 22 runtime dependencies (no change)
All tracked output (25)
Runtime dependencies (22)
Baseline: main_@_240a9137___2026-09-16 · gzip is the comparison metric · changes below 16 B gzip are ignored |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (18)
Included review availability: Your plan provides up to 4 included reviews per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe change adds hydration-aware script status handling. Server-rendered statuses are stored in the Nuxt payload and held on the client until suspense resolves. A new helper manages the held and live status values. Unit tests cover its transitions. A Nuxt fixture and end-to-end tests verify hydration behavior across multiple script triggers. Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Merge Risk: ⚪ Minimal · up to The hydration status behavior is covered across server rendering, client hydration, and live transitions, with no remaining merge-blocking issue identified. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 7 files. (11 skipped: 11 unsupported.)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🤖 READY · 88/100
|
📚 Description
If a page renders
{{ status }}from a script withtrigger: 'client', hydration reports a mismatch. The server rendersawaitingLoad, and the client rendersloading. Registry scripts that fix their trigger toclienthit this with no config: Vercel Analytics, Cloudflare Web Analytics, SpeedCurve, and npm-mode PostHog. #918's sweep found it on thebasicandspeedcurvefixtures (PC-20).Unhead runs a
clienttrigger synchronously insideuseScript(), soload()setsloadingduring setup. The defaultonNuxtReadytrigger,visible,manual, andserverdo not mismatch. They either change status after hydration or change it the same way on both sides.The server now writes any status other than
awaitingLoadto the payload. The client status ref reports that value untilapp:suspense:resolve, then shows the live status. The trigger and loader still run at the same moment. Only what templates and watchers read waits.I first computed the server status from the trigger instead of using the payload. That breaks when setup also calls
load(). A status ref cannot tell those explicit calls apart from trigger loads, because Unhead calls the samescript.load. The payload costs nothing unless a script loads on the server.Load timing, measured on production builds, alternating before and after in headless Chromium. Values are medians in ms from navigation start, 20 runs (30 for
clientat 4x CPU):load()called<script>inserteddefaultonNuxtReadyclientdefaultonNuxtReadyclientWith the fix, a
statuswatcher on a client-trigger page seesawaitingLoad > loading > loaded. Before, it sawloading > loaded, because the ref was created after the status had already changed.Loose ends:
hydrate-on-visible, hydrates afterapp:suspense:resolve. If it renders a client-trigger status, it still mismatches. I did not find a clean per-component signal for this.basic/reload-trigger(#use-call-count0 vs 1) is a separate fixture quirk. Unhead callsuse()eagerly on the client, and the page renders a counter thatuse()increments. I left it alone.setup()directly because test(e2e): build fixtures with template comments stripped #918 has not merged. Once test(e2e): build fixtures with template comments stripped #918 lands, convert it tosetupFixture().