Skip to content

Commit 7b938f7

Browse files
fix(platform): install analog-owned SSR renderer to bypass Nitro auto-template
Nitro's resolveRendererOptions (nitro/dist/_chunks/nitro.mjs:296-311) auto-detects index.html and installs renderer-template[.dev] as renderer.handler. nitro/vite's configResolved branch that would swap in its SSR-dispatch renderer only runs when both renderer.handler and renderer.template are empty (nitro/dist/vite.mjs:574), which never holds for a typical app with an index.html at root. Result: every HTML request returns the raw template instead of routing through the SSR service. analogNitroPlugin now registers an explicit #analog/ssr-renderer virtual in nitro.options.virtual and sets renderer.handler to it. The handler short-circuits to the raw template when x-analog-no-ssr is on the response (set by injectAnalogRouteRuleHeaders from routeRules with ssr: false) and otherwise dispatches through fetchViteEnv to the SSR service env. The SSR-wrapper service entry keeps its own response shape; the no-ssr request-header check stays in place as a defense-in-depth path. Refs #2035 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4b7761c commit 7b938f7

1 file changed

Lines changed: 46 additions & 2 deletions

File tree

packages/platform/src/lib/nitro/analog-nitro-plugin.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,21 @@ export function analogNitroPlugin(options: Options = {}): Plugin {
212212
}
213213
});
214214

215+
// Override Nitro's auto-detected template-serving renderer with one
216+
// that routes HTML requests to our SSR service. Nitro's
217+
// `resolveRendererOptions` finds `index.html` at the project root
218+
// and installs `internal/routes/renderer-template[.dev]`, which
219+
// just serves the raw template. nitro/vite's own SSR-routing
220+
// renderer only auto-installs when both `renderer.handler` and
221+
// `renderer.template` are empty (vite.mjs:574), which never holds
222+
// for a typical app — so we install our own renderer virtual
223+
// explicitly here.
224+
nitro.options.virtual['#analog/ssr-renderer'] =
225+
generateSsrRendererVirtual(readIndexHtml());
226+
nitro.options.renderer ??= {};
227+
nitro.options.renderer.handler = '#analog/ssr-renderer';
228+
delete nitro.options.renderer.template;
229+
215230
injectAnalogRouteRuleHeaders(nitro);
216231

217232
await wirePrerender(nitro, options, context, apiPrefix);
@@ -231,10 +246,39 @@ export function analogNitroPlugin(options: Options = {}): Plugin {
231246
return plugin;
232247
}
233248

249+
/**
250+
* Builds the h3 handler installed as Nitro's `renderer.handler`. Short-circuits
251+
* `ssr: false` routes to the raw client template; otherwise dispatches the
252+
* request to the SSR service env (`fetchViteEnv("ssr", req)` works in dev via
253+
* the env-runner and in prod via the `__nitro_vite_envs__` global set up by
254+
* nitro/vite's `prodSetup`).
255+
*/
256+
function generateSsrRendererVirtual(template: string): string {
257+
return `
258+
import { defineHandler } from 'nitro/h3';
259+
import { fetchViteEnv } from 'nitro/vite/runtime';
260+
261+
const TEMPLATE = ${JSON.stringify(template)};
262+
263+
export default defineHandler(async (event) => {
264+
event.res.headers.set('content-type', 'text/html; charset=utf-8');
265+
// 'x-analog-no-ssr' is stamped on response headers by
266+
// injectAnalogRouteRuleHeaders for routeRules with \`ssr: false\`. Nitro
267+
// applies routeRule headers to the response before the renderer fires,
268+
// so we can short-circuit by reading them here.
269+
if (event.res.headers.get('x-analog-no-ssr') === 'true') {
270+
return TEMPLATE;
271+
}
272+
return fetchViteEnv('ssr', event.req);
273+
});
274+
`;
275+
}
276+
234277
/**
235278
* Walks Nitro's resolved routeRules and stamps `x-analog-no-ssr: true` onto
236-
* any rule with `ssr: false`. Analog's SSR service wrapper reads this header
237-
* to short-circuit the renderer and return the raw template.
279+
* any rule with `ssr: false`. Kept as a response-header hint for downstream
280+
* consumers (CDN, edge logic); the actual SSR short-circuit happens inside
281+
* the SSR renderer virtual above.
238282
*/
239283
function injectAnalogRouteRuleHeaders(nitro: Nitro): void {
240284
const routeRules = nitro.options.routeRules as

0 commit comments

Comments
 (0)