Repository navigation
Description
Summary
ctx.rendered() (the render barrier) is experimental and Phase 1 only: it is unavailable when the matched route tree uses loading(). Calling it from a streaming tree throws. This issue tracks lifting that limitation so rendered() works under streaming.
Current behavior
rendered() lets a DSL loader await until the non-loader render tree has settled, then read handle data via ctx.use(handle). Today it hard-rejects streaming trees:
- Guard:
src/router/loader-resolution.ts:314-322throws whenreqCtx?._treeHasStreamingis set:// Guard: reject streaming trees if (reqCtx?._treeHasStreaming) { throw new Error( `ctx.rendered() is not supported when the matched route tree uses loading(). ` + `Streaming handlers may not have settled when rendered() resolves. ...` ); }
- The flag is computed once per request via
treeHasStreaming(ctx.entries)and stashed on the request context (src/router/match-middleware/cache-lookup.ts:197-202and:582-586; field documented atsrc/server/request-context.ts:276). - The public type is marked
**Experimental.**and "non-streaming trees (noloading())" atsrc/types/loader-types.ts:72.
Why it's blocked
In a streaming tree, handlers behind loading() are React components whose promises resolve during RSC serialization — after resolveAllSegments() returns. So at barrier-resolution time, handle data pushed by those handlers may be incomplete. Resolving the barrier then would hand the loader a partial/empty handle.
Design options (from the rendered-barrier doc)
Supporting streaming requires tracking which handles come from settled vs unsettled handlers, then either:
- Wait for full settlement before resolving the barrier — correct, but defeats the point of streaming for that request.
- Partial snapshot — resolve the barrier with the settled handles and expose explicit "may be incomplete" semantics for the rest.
Both add real complexity; the Phase 1 decision was to avoid it until there's a concrete use case. Pick/validate an option as part of this work.
Code touchpoints
src/router/loader-resolution.ts:305-322— therendered()implementation + the streaming guard to lift/replace.src/router/match-middleware/cache-lookup.ts— where the barrier resolves and_treeHasStreamingis set; the resolution point would need to account for unsettled streaming handlers.src/server/request-context.ts:276—_treeHasStreamingflag.src/types/loader-types.ts:72— drop/soften**Experimental.**and the "non-streaming" wording once it lands.packages/rangojs-router/docs/internal/rendered-barrier.md— update "Phase 1 scope" / "Why not streaming" and the status line.
Acceptance criteria
await ctx.rendered()in a DSL loader on a tree withloading()resolves with correct (or explicitly-scoped) handle data instead of throwing.- The chosen semantics (full-wait vs partial-snapshot) are documented and predictable.
- The handler-invoked-loader guard and the deadlock guard remain (only the streaming guard changes).
Tests
src/router/__tests__/rendered-barrier.test.ts:128(rendered() guard: streaming tree) flips from "throws" to the new behavior.- New e2e covering a
loading()tree +rendered(), in both dev and production (per the repo's dev+prod mandate).
Notes
This was a documented Phase 1 deferral, not previously tracked work; the doc's own conclusion was "unclear value, avoid for now," so confirm there's a real use case before investing.
Activity
docs(router): warm rendered-barrier + why-web-streams; rendered() loa…