On this page

Advanced concepts

These concepts explain why Rebask stores resume state, rerenders part of the tree on a cache hit, and namespaces cache entries by deployment.

What a resume stores

A PPR shell is server-rendered HTML plus state that lets Rebask finish the page later. During prerendering, React's Fizz renderer returns two things:

  • A prelude: the HTML that is already complete, including Suspense fallbacks.
  • A postponed state: where the render stopped at a <Suspense> boundary and the structure React needs to replay.

Rebask adds a third piece: the "use cache" and durableCache entries the shell read. That map is serialized with the postponed state and stored in the page cache entry.

The shell is served long after it was rendered, but the RSC payload that hydrates it is rendered per request. When the shell is resumed, Rebask reads the pinned entries before the live cache, so the cached HTML and the new payload use the same values. Without those entries, a cache change between build and request could leave the resumed HTML with the old value while the new payload used the new one.

At request time Rebask:

  1. Looks up the shell and parses its resume state.
  2. Pins the stored cache entries in the request context.
  3. Renders the RSC tree for this request.
  4. Calls React's resume() to stream the dynamic holes into the shell.
  5. Promotes the result to a more specific shell or Complete HTML when possible.

To learn more, see PPR, Page Caching, and Cache Handler.

Why the RSC tree renders again

React can resume an HTML render, but it cannot resume the server execution that produced the RSC data.

Fizz, the HTML renderer, can stop at a <Suspense> boundary and record enough state to continue later. That is what resume() consumes. The postponed state is a replayable render state, not a serialized promise or call stack.

Flight, the RSC format, is a serializable description of a component tree. It has no equivalent resume() for the server render. It can stream promise chunks to the client, but the server-side promise continuation and its closed-over state are gone once the request ends.

So Rebask rerenders the RSC tree to produce a fresh Flight payload, then resumes the existing HTML from the postponed state. Only the postponed part streams. Values the shell already read from "use cache" come from the pinned entries instead of being fetched again, but components rerun and work inside the postponed part still runs.

This is also why request I/O can't be baked into a shell: headers(), cookies(), and other request reads produce values that only exist for one request. Put them under <Suspense> so PPR can resume them. Outside <Suspense>, the route has no static shell. See Server Runtime APIs.

Deployment IDs and cache namespaces

A production build gets a new deploymentId by default. Page, RSC, and "use cache" cache keys include it. durableCache keys do not, because those values are meant to outlive a deployment.

The old entries are not deleted when you deploy. Their keys become unreachable, and the cache handler removes them by lifetime or eviction. That is why a new build looks like an empty cache without Rebask flushing it.

Rebask generates a new deploymentId for each production build. Override it only when an external system supplies a unique ID for each deployment. Use durableCache for data that must survive builds. See Server Cache keys for the full key table.

Internal headers

Rebask uses request and response headers to coordinate the router, the server, and platform adapters.

These headers are not public APIs and can change between releases. Treat the mas implementation details
HeaderDirectionPurpose
x-rebask-treeRequestAsk for the route tree JSON used by client navigations and deployment checks.
x-rebask-shellRequestAsk for a route segment's Flight shell.
x-rebask-tailBothIdentify a shell by byteLength:sha256. The server echoes it only when the shell matches.
x-rebask-trailerResponseNonce separating the RSC payload from its trailing search-param record. The record has to wait until the render ends, and the client needs it to decide whether a cached segment is still reusable.
x-rebask-cacheResponseOrigin cache outcome: HIT, STALE, or MISS. Absent when the adapter owns page caching.
x-rebask-prerenderResponse1 when serving a cached shell plus streamed dynamic content.
x-rebask-deployment-idResponseDeployment id of the server that produced the response.
x-rebask-resumeRequestMarks a POST as a resume. The adapter validates the platform credential, then Rebask reads the resume state from the request body.
x-rebask-fillRequestTells Rebask what a platform cache fill should render: html for a page or shell, rsc for an RSC payload. Only the adapter sets it.
x-rebask-pathnameRequestPlatform-supplied original pathname, stripped before application code.
x-rebask-proxy-onlyBothDevelopment-only marker for a request that only the proxy should answer.

Only the three diagnostic headers in DevTools are meant for inspection. x-rsc-action belongs to React's Flight protocol, not Rebask. Platform adapters may add their own transport headers and route suffixes. See Vercel for its request flow.

x-rebask-trailer

If a segment reads searchParam("sort"), the response body ends with \r\n--<nonce>\r\n{"used":{"sort":"price"}}, where the nonce is the header value. The client splits there, decodes only the RSC bytes, and records that the segment depends on sort=price:

Requests
/products?sort=price&utm=a
utm differs
/products?sort=price&utm=b
same sort
/products?sort=date
different sort
Shared shell
/productsno search params
Ready for any query string
Cache entries
[sort=price]2 requests
shell prefix
resume: Price ordering
utm never enters the key
[sort=date]1 request
shell prefix
resume: Date ordering
separate entry

Shells leave search params out so one shell can serve any query string for a path it matches. When the shell resumes, the client caches the resumed segment by the params the render read. A different sort gets a separate entry, while an unrelated utm change reuses the same one. The trailer reports which params were read.

The search params are only known when rendering ends, so they are appended. Sending them as a response header would require buffering the RSC stream.