Capítulo 276 de 456

Implementing PPR in an Adapter

Core Idea

For Partial Prerendering (PPR) routes, an adapter seeds a cached HTML shell + postponed state at build time, then at request time streams the cached shell concatenated with a resumed render.

Key Concepts

  • outputs.prerenders[].fallback.filePath: path to the generated fallback shell (HTML) to cache.
  • outputs.prerenders[].fallback.postponedState: serialized postponed state needed to resume rendering.
  • Resume flow: response = cached shell stream + resumed stream, produced by invoking handler with the postponed state.
  • requestMeta.onCacheEntryV2: callback fired when a response cache entry is looked up or generated; use it to persist updated shell/postponed data. Returns false to continue normal flow, true if the adapter already wrote the response itself.
  • requestMeta.onCacheEntry: deprecated predecessor of onCacheEntryV2; prefer the V2 callback.

Code Examples

import { readFile } from 'node:fs/promises'

async function seedPprEntries(outputs: AdapterOutputs) {
  for (const prerender of outputs.prerenders) {
    const fallback = prerender.fallback
    if (!fallback?.filePath || !fallback.postponedState) continue

    const shell = await readFile(fallback.filePath, 'utf8')
    await platformCache.set(prerender.pathname, {
      shell,
      postponedState: fallback.postponedState,
      initialHeaders: fallback.initialHeaders,
      initialStatus: fallback.initialStatus,
      initialRevalidate: fallback.initialRevalidate,
      initialExpiration: fallback.initialExpiration,
    })
  }
}
await handler(req, res, {
  waitUntil,
  requestMeta: {
    postponed: cachedPprEntry?.postponedState,
    onCacheEntryV2: async (cacheEntry, meta) => {
      if (cacheEntry.value?.kind === 'APP_PAGE') {
        const html = cacheEntry.value.html?.toUnchunkedString?.() ?? null
        await platformCache.set(meta.url || req.url || '/', {
          shell: html,
          postponedState: cacheEntry.value.postponed,
          headers: cacheEntry.value.headers,
          status: cacheEntry.value.status,
          cacheControl: cacheEntry.cacheControl,
        })
      }
      return false // true only if the adapter already wrote the response
    },
  },
})
  • O que demonstra: seed do cache de shell+postponedState no build e atualização contínua via onCacheEntryV2 em runtime.

Reference Tables

StepAction
1. SeedRead fallback.filePath + fallback.postponedState at build time, store in platform cache
2. ServeStream cached shell first, then pipe resumed chunks from handler(req, res, { requestMeta: { postponed } })
3. UpdaterequestMeta.onCacheEntryV2 persists refreshed shell/postponed data back to the platform cache

Key Takeaways

  1. PPR support in an adapter is a three-part cycle: build-time seed, request-time resume, ongoing cache update via onCacheEntryV2.
  2. onCacheEntryV2 supersedes the deprecated onCacheEntry; internal onCacheCallback abstractions should wire to V2.
  3. The response the client sees is literally shell bytes + resumed bytes concatenated into one HTTP response.

Connects To

  • ch277 runtime-integration: ctx.waitUntil and onCacheEntryV2 in the broader runtime-integration contract.
  • ch279 output-types: full shape of outputs.prerenders[].fallback.