#4622·nitro

renderer: tree-shake unused rendu context helpers in production templates

Author: pi0xCreated Sep 13, 2026Updated Sep 13, 2026

Summary

The production renderer template (src/build/virtual/renderer-template.ts) compiles the HTML template when Nitro builds, then emits import { renderToResponse } from 'rendu'. renderToResponse always creates the full render context, so the output always includes rendu's cookie utilities (cookie-es parse/serialize plus the lazy $COOKIES proxy), even when the template never uses $COOKIES or setCookie.

Proposed change

rendu is adding a build-time codegen API, compileTemplateToModule(). It generates an ES module that imports and passes in only the render context variables the template actually uses. There is also a lower-level renderContextToResponse() with individually importable createRenderResponse / createRenderURL / createCookies / createSetCookie / createRedirect helpers.

This API is not released yet. The Nitro change is blocked until a rendu release includes it.

Bundling generated modules with rolldown against rendu's dist, with srvx left out:

Case Size Cookie code
Template using only {{ title }} and $URL ~3.0 KB no
Template using setCookie and $COOKIES ~9.2 KB yes
Current renderToResponse import ~8.5 KB always

The production branch of renderer-template.ts would become roughly:

typescript
import { hasTemplateSyntax, compileTemplateToModule } from "rendu";

const isVite = nitro.options.builder === "vite";
const renderModule = compileTemplateToModule(html, {
  contextKeys: ["fetch", "serverFetch", ...(isVite ? ["fetchViteEnv"] : [])],
});
return /* js */ `
  ${renderModule}
  import { fetch, serverFetch } from 'nitro/app'
  ${isVite ? `import { fetchViteEnv } from "nitro/vite/runtime"` : ""}
  const context = { fetch, serverFetch${isVite ? ", fetchViteEnv" : ""} }
  export const rendererTemplate = (request) => render(request, context)
`;

The dev handler (renderer-template.dev.ts) can keep using compileTemplate + renderToResponse, since bundle size doesn't matter there.

Related bug (likely)

Today the production template is compiled with contextKeys: [...RENDER_CONTEXT_KEYS], but the context also contains fetch, serverFetch and (with Vite) fetchViteEnv. In contextKeys mode a compiled template can only see the listed names. So serverFetch / fetchViteEnv in a production template probably throw a ReferenceError, and fetch falls back to globalThis.fetch instead of Nitro's fetch. This is reasoned from the code and not reproduced in a running Nitro app. Dev works because it uses with() mode. Passing these names as contextKeys (as in the snippet above) fixes it.

Tasks

  • Release rendu with compileTemplateToModule
  • Switch the production renderer template to compileTemplateToModule
  • Add a test for serverFetch / fetch access in production renderer templates