Skip to content

feat: render Server Component output with live params on soft navigation - #177

Draft
uhyo wants to merge 1 commit into
masterfrom
claude/issue-174-discussion-22aegy
Draft

feat: render Server Component output with live params on soft navigation#177
uhyo wants to merge 1 commit into
masterfrom
claude/issue-174-discussion-22aegy

Conversation

@uhyo

@uhyo uhyo commented Aug 28, 2026

Copy link
Copy Markdown
Owner

Completes the remaining half of #174: under file-system routing, a Server Component page or layout kept its build-time params (and rendered output) after soft client-side navigation between pages of the same dynamic route. The destination's pre-rendered output existed in the build but was unreachable from the browser.

Approach

Server Component route nodes are no longer baked into the route definitions as rendered elements. Instead, each is represented by a client slot (FsRouteSlot), and navigation is handled by FUNSTACK Router in its entirety — no changes to the client entry, no navigation listeners.

  • Build: for each Server Component node, one RSC chunk is pre-rendered per params combination occurring among the generated pages, registered through the existing defer registry. Chunks flow through the existing content-hash/ID-replacement pipeline, so immutable caching holds for everything below the HTML. The params→chunk map is baked into the slot's props; the page's own output is additionally inlined as initial, so first load and hydration are unchanged (no extra fetch).
  • Client: the slot reads the live params of the current match via useRouteParams(route). While they equal the payload's build params it renders initial; after a soft navigation it suspends on the destination's chunk inside the router's transition, so the old page stays visible until the new output is ready. Chunks are cached per URL, making back/forward instant.
  • Fallback: a params combination that was never generated, or a chunk fetch failure (e.g. version skew after a redeploy removed old hashed files), falls back to a full page load of the destination URL, with a sessionStorage guard against reload loops.
  • Dev: the dev server already serves defer-registry entries on demand, so soft navigation works there with no new serving code.

To keep runtime.tsx unit-testable outside a Vite RSC environment, its impure edges (defer registry, virtual:funstack/config, #rsc-client) live in a new fs-routes/entries.tsx that injects a small host object into the pure core. The public createFsRoutesEntries API is unchanged.

Behavior changes

  • Server Component pages and layouts now show the live params after soft navigation (the bug fix).
  • Server Component layouts now receive only the params of dynamic segments at or above them (e.g. a [lang]/ layout sees { lang }, not a descendant { slug }), matching their per-combination pre-rendering and Next.js semantics. Previously they received the full page params.
  • Each page's main payload no longer embeds every other page's rendered output; shared output is deduplicated into per-combination chunks (the e2e fixture emits exactly 15 chunks for 14 pages). Per-page payloads shrink accordingly.
  • Soft navigation to a URL whose params were never generated now falls back to a hard navigation instead of silently rendering another page's params.

Testing

  • Unit: 50 tests pass, with new coverage for chunk registration/dedup per combo, layout param restriction, chain-only initial inlining, and stable route ids.
  • E2E: full production (39) and dev-server (32) suites pass, including the issue's exact repro flipped to the fixed expectation (/ja → /en now renders en in the Server Component page), a new Server Component layout+page fixture under [lang]/info/, back/forward navigation, and no-JS-error sweeps.
  • pnpm typecheck, pnpm lint, pnpm format:check clean; docs site builds.

Docs: the File-System Routing page's "known limitation" note is replaced with a description of how both component kinds stay live, the layout params rule, and the fallback behavior.

Closes #174

🤖 Generated with Claude Code

https://claude.ai/code/session_01ErSPPfkksH1SqMULJdivfG


Generated by Claude Code

Under file-system routing, a Server Component page or layout kept its
build-time params (and output) after soft client-side navigation between
pages of the same dynamic route, with the destination's pre-rendered
payload unreachable in the build output (#174, remaining half).

Server Component route nodes are now represented by a client slot
(FsRouteSlot) in the route definitions instead of a baked element. The
build pre-renders one RSC chunk per node per params combination through
the existing defer registry (content-hashed, immutably cacheable), and
bakes the params-to-chunk map into the slot. The slot reads the live
params of the current match via the route object and renders the inline
build-time output while they match the page's own params, or suspends on
the destination's chunk after a soft navigation — inside the router's
transition, so the old page stays visible until the new output is ready.
A failed chunk resolution (a params combination that was never generated,
or a fetch failure after a redeploy) falls back to a full page load, with
a sessionStorage guard against reload loops.

Since chunks are shared per params combination, this also stops each
page's main payload from embedding every other page's rendered output,
and Server Component layouts now receive only the params of segments at
or above them (matching their per-combination pre-rendering) rather than
the full page params.

Closes #174

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErSPPfkksH1SqMULJdivfG
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fs-routes: client components receive stale params on soft navigation, with no way to read live ones

2 participants