skip to content

A streamed route defers a section whose data shows the visitor may not see this page, but the shell is already sent. What are your options?

level: seniorimportance: should knowfreq 50%

answer

  1. the shell already promised success
  2. promote the check above the flush
  3. a client-side bounce arrives too late
  4. existence and permission are response decisions

basics

~10 s

Three, and only one is clean: promote the check above the shell flush so the response can still refuse or redirect; render a denial state in that region; or navigate from the client afterwards.

solid answer

~50 s

Once the shell is out, the response has already promised success, so the deferred level cannot refuse the request — it can only change what is in its own placeholder. The fix is to move the check above the flush: await it in the outermost level, or run it in a hook before route resolution, so a redirect or an error status is still possible. The cost is first-byte time for that one check, and everything else on the route keeps streaming. The alternatives are damage control: a denial state rendered into the region leaves the surrounding page visible and returns success, and a client-side navigation from the late chunk needs script, leaves a history entry, and never reaches a consumer that only reads the HTML. Treat authorization and existence checks as unsafe to defer.

go deeper

for a junior

Take away the ordering rule: a page that has started sending cannot take it back. Checks that decide whether the page should exist have to run before anything is sent.

for a middle

Explain what each option actually delivers — a refusal, a success with a denial region, or a success followed by a client-side jump — and why only the first keeps the response honest.

for a senior

Demonstrate the diagnosis and the guardrail: spot that the check drifted below the flush when the region was deferred, and make the test assert the status rather than the markup.

for a principal

Set the boundary for the codebase: authorization lives in a shared enclosing level or a pre-resolution hook, so no future refactor can move it below the commit point without the review noticing.

## Why the question has no clean late answer A streamed route commits its envelope when the shell flushes: status line, headers, cookies. A level that resolves after that is writing into an already-addressed envelope. So a deferred section that learns *the visitor is in the wrong tenant*, *the trial lapsed*, or *this record belongs to someone else* has lost access to every response-level tool — 403, a redirect to a sign-in route, a `Set-Cookie` that clears a stale session. It owns exactly one thing: the markup that fills its placeholder. That is why the interesting part of this question is not the recovery but the placement. ## The three options, and what each costs | option | what the client gets | what it costs | |---|---|---| | promote the check above the flush | a redirect or an error status, no page at all | the shell waits for that one check | | render a denial state in the region | success, page frame visible, one region says no | surrounding content was still served | | navigate from the client in the late chunk | success, page shown, then a jump elsewhere | needs script, adds history, invisible to HTML-only consumers | 1. **Promote the check.** Await it where the shell's flush already waits — the outermost level of the chain — or lift it out of the chain entirely into a hook that runs before route resolution. The route can then answer with a redirect or a refusal, exactly as a non-streamed route would. The price is real but bounded: first byte now includes that check's latency, and every *other* slow part of the route still streams behind the shell. 2. **Render a denial state.** Sometimes correct on purpose: a page whose frame is public but whose panel is restricted can legitimately return success and show *you do not have access to this section*. It is wrong when the whole route is restricted, because the page frame, the navigation and any already-revealed regions were served to someone who should not have had them. 3. **Navigate from the client.** The late chunk carries script that sends the browser elsewhere. This is the option teams reach for when the check cannot be moved, and every one of its weaknesses follows from the same fact: the document already returned success with content. The visitor saw the shell; a consumer that never runs script sees the page and nothing else; the history stack now holds the page they were bounced off, so the back button returns to it. ## Which levels are therefore unsafe to defer The rule generalises past authorization. Anything whose result could have changed the response itself must resolve before the flush: - **does this thing exist** — a missing record is a 404, and 404 is a status; - **may this visitor see it** — a refusal is a status, a redirect, or both; - **does the session need rotating or clearing** — that is a `Set-Cookie` header; - **is this page cacheable, and by whom** — `Cache-Control` and `Vary` are headers; - **what identity is this page rendered for** — it decides several of the above at once. Everything that merely fills a region — recommendations, counts, feeds, related items — is safe below the flush, because the worst case is a region that degrades. ## Getting the leak in the first place In practice this bug is rarely designed in; it drifts in. A route starts with a permission check at the top. Later somebody moves the data read into a deferred region for a faster first byte, and the check travels with it. Nothing fails loudly: the page renders, the tests pass, and the refusal simply stops being a refusal. What keeps it from drifting: - put the check in a level that *structurally* cannot be deferred — an enclosing layout that every route in the section shares, or a pre-resolution hook; - make the authorization decision return the data the page needs, so the region cannot be moved without moving the check; - assert in tests that an unauthorised request to the route produces a redirect or an error status, not a success document — a check that asserts the rendered markup will keep passing after the drift; - watch for the smell in review: a deferred region whose result is `if (!allowed) ...`. ## If you genuinely cannot move it Sometimes the check depends on data that is expensive and only the deep level has. Then choose deliberately: either this route does not stream at all (the response is held until the decision is made, trading first byte for correctness), or the route is restructured so a cheap pre-check — a token claim, a cached membership flag — runs above the flush and the expensive confirmation only refines what the region shows.

  • Where does a not-found check belong on a streamed route?
    Above the flush, for the same reason as a permission check: 404 is a status, and the status leaves with the shell. If existence can only be established by a deferred read, either that read moves up, or the route accepts serving a success document with an empty state in the region — a real choice, but one to make deliberately.
  • How do you stop a promoted check from becoming a first-byte problem across many routes?
    Put it once in the enclosing level that a whole section shares, so it runs a single time per request rather than per route, and keep it cheap — a token claim or a cached membership flag rather than a full join. Expensive confirmation can stay deferred as long as it only refines what a region shows.

saying these in an interview costs you the question

  • Returns a refusal status from the deferred level after the flush
  • Thinks a client-side bounce leaves no trace of the page
  • Defers an authorization check to improve first-byte time
  • Assumes HTML-only consumers follow the late navigation
  • Believes one awaited check stops the route from streaming