Repository navigation
feat(router)!: loader ssr:false replaces stream:"navigation" - #820
Merged
Merged
Conversation
The SSR-completeness opt-in now uses the knob the API already has:
loader(Def, { ssr: false }) — the same option loading(fallback, { ssr:
false }) uses for the fallback. Semantics are unchanged: document renders
await the flagged loader before first flush (data, handle pushes, and a
thrown notFound()'s status are deterministically in the SSR'd document),
client navigations keep streaming it, per-loader scoping stays, and under
ppr it remains the bake lane. The stream option is REMOVED (breaking, per
pre-release API hygiene); passing it now throws a targeted error pointing
at ssr: false on both DSL surfaces (server urls() and clientUrls()).
Renamed through: LoaderOptions, both loader() helpers, the clientUrls
LoaderItem/ClientUrlLoaderRecord shapes and projection derivation
(awaitedLoaderIndices wire format unchanged), the materialization seam,
the rendered() deadlock message, and every fixture/doc/skill mention.
Internal names (awaitBeforeFlush, _awaitBeforeFlushLoaderIds) stay — they
describe the semantic, not the API.
Red-proven: 7 renamed/new unit pins fail on the pre-rename runtime
(stamping via ssr:false, boolean validation, removed-option rejection on
both surfaces, projection round-trip); e2e green both modes on
vite-rsc-demo client-shop-stream (6/6) and test-app shell-cache bake lane
+ clientUrls ppr group (3/3 production, dev counterparts green).
ivogt
marked this pull request as ready for review
July 29, 2026 09:57
1 of 5 tasks
ivogt
added a commit
that referenced
this pull request
Sep 23, 2026
… ssr:false
Only loader(Def, { ssr: false }) bakes at PPR capture since #813/#820. Update the capture diagnostics (bake cost, no usable shell, identity refusal, rejected loader, redirect/notFound refusal) and the cookies()/headers() capture error, which still advised loading() or nested promises. Share one PPR_LANE_HINT. Make resolveLoaderData the canonical statement of the lane rule and point other comments and docs at it. Fix stale JSDoc examples, resolve the #803 conflict leftover in feature-map.md, and register the /shell-cache/guard fixture loader with ssr: false so the identity guard is exercised again.
ivogt
added a commit
that referenced
this pull request
Sep 23, 2026
… ssr:false (#871) Only loader(Def, { ssr: false }) bakes at PPR capture since #813/#820. Update the capture diagnostics (bake cost, no usable shell, identity refusal, rejected loader, redirect/notFound refusal) and the cookies()/headers() capture error, which still advised loading() or nested promises. Share one PPR_LANE_HINT. Make resolveLoaderData the canonical statement of the lane rule and point other comments and docs at it. Fix stale JSDoc examples, resolve the #803 conflict leftover in feature-map.md, and register the /shell-cache/guard fixture loader with ssr: false so the identity guard is exercised again.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
stream: "navigation"grew the API vocabulary for a concept the DSL already expresses.loading(fallback, { ssr: false })has meant "this fallback has no SSR presence — document renders deliver real data" since before the streaming work; the loader-level opt-in shipped as a newstreamenum instead of the same knob. Consumers learning one option had to discover the other under a different name, and thestreamenum implied more future values than the feature has.Before vs. after
Behavior is identical: the flagged loader is awaited before first flush on document requests (its data, handle pushes, and a thrown
notFound()'s 404 status are deterministically in the SSR'd HTML; no fallback paints for it), client navigations keep streaming it behind the fallback, and underpprit stays the bake lane (executes at capture, settled return is shell material, nested promises stay live holes).Passing the removed option is a targeted error, not a silent ignore:
End-to-end consumer example
One vocabulary throughout:
ssr: falseon a loader = "no SSR streaming for this loader" (awaited, document-complete);ssr: falseonloading()= "no SSR presence for this fallback" (all of the route's loaders awaited). Both read as the same fact from opposite ends.Semantics (all unchanged — this is a rename)
ssr: falseloaderloading()/Suspensectx.rendered()in the loaderssr: false)intercept()loaderssr: falsecan never apply — navigations only)ssr: true/ omittedCode changes
src/types/loader-types.ts—LoaderOptionsis{ ssr?: boolean }; docs rewritten (also fixes a stale claim that the flag "does NOT change PPR capture behavior" — it has been the bake lane since feat(client-urls): ppr as a projected group path option — shell caching for group routes #817).src/route-definition/dsl-helpers.ts—loader()validatesssr(boolean), rejects the removedstreamwith the pointer message, stampsawaitBeforeFlushfromssr === false && ctx.isSSR(same per-isSSR mechanism asloading({ ssr: false }), which the adjacent comment already cross-referenced).src/client-urls/client-urls.ts+types.ts— group DSLloader()renames the same way (LoaderItem.ssr,ClientUrlLoaderRecord.ssr, intercept rejection message);src/client-urls/server-projection.tsderivesawaitedLoaderIndicesfromssr === falseand materializes{ ssr: false }onto the server loader. The projection wire format (awaitedLoaderIndices) is unchanged — previously serialized projections keep working.src/router/loader-resolution.ts— therendered()deadlock error namesssr: false.awaitBeforeFlush,_awaitBeforeFlushLoaderIds, comment-level references updated to the new spelling.client-urls.md, feature-map, design docs), and 20+ skills swept; two e2e titles renamed (client-shop loader ssr:false,ssr:false loader bakes…).Test plan
ssr: falsestampsawaitBeforeFlushon SSR evaluation only,ssr: trueis an inert explicit default, boolean validation, removed-streamrejection on both DSL surfaces, clientUrls record carriesssr: false, projection round-trip materializes the await on SSR entries only.client-shop-stream6/6 (document-complete data/title, real 404 with no warm-up, navigation still streams the flagged loader); test-appshell-cachebake-lane pins + clientUrls ppr group route 3/3 production and green in dev — the renamed option threads DSL → projection → materialization → runtime → ppr bake.test:unit:all, lint, format, bucketing.Notes
stream: "navigation"is removed outright per pre-release API hygiene — no alias, but the rejection message names the replacement on both surfaces.loading()'sssr: falseis untouched; this PR only brings the loader option into the same vocabulary.stream: "navigation"→ssr: false.