@analogjs/vite-plugin-nitro 2.4.0-alpha.3 → 2.4.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,130 +1,58 @@
1
- /**
2
- * SSR renderer virtual module content.
3
- *
4
- * This code runs inside Nitro's server runtime (Node.js context) where
5
- * event.node is always populated. In h3 v2, event.node is typed as optional,
6
- * so we use h3's first-class event properties (event.path, event.method) where
7
- * possible and apply optional chaining when accessing the Node.js context for
8
- * the Angular renderer which requires raw req/res objects.
9
- *
10
- * h3 v2 idiomatic APIs used:
11
- * - defineHandler (replaces defineEventHandler / eventHandler)
12
- * - event.path (replaces event.node.req.url)
13
- * - getResponseHeader compat shim (still available in h3 v2)
14
- */
15
- export function ssrRenderer(templatePath) {
16
- return `
17
- import { readFileSync } from 'node:fs';
18
- import { createFetch } from 'ofetch';
19
- import { defineHandler, fetchWithEvent } from 'h3';
1
+ export const ssrRenderer = `
2
+ import { eventHandler, getResponseHeader } from 'h3';
20
3
  // @ts-ignore
21
4
  import renderer from '#analog/ssr';
5
+ // @ts-ignore
6
+ import template from '#analog/index';
22
7
 
23
- const template = readFileSync(${JSON.stringify(templatePath)}, 'utf8');
24
- const normalizeHtmlRequestUrl = (url) =>
25
- url.replace(/\\/index\\.html(?=$|[?#])/, '/');
26
-
27
- export default defineHandler(async (event) => {
28
- event.res.headers.set('content-type', 'text/html; charset=utf-8');
29
- const noSSR = event.res.headers.get('x-analog-no-ssr');
30
- const requestPath = normalizeHtmlRequestUrl(event.path);
8
+ export default eventHandler(async (event) => {
9
+ const noSSR = getResponseHeader(event, 'x-analog-no-ssr');
31
10
 
32
11
  if (noSSR === 'true') {
33
12
  return template;
34
13
  }
35
14
 
36
- // event.path is the canonical h3 v2 way to access the request URL.
37
- // event.node?.req and event.node?.res are needed by the Angular SSR renderer
38
- // which operates on raw Node.js request/response objects.
39
- // During prerendering (Nitro v3 fetch-based pipeline), event.node is undefined.
40
- // The Angular renderer requires a req object with at least { headers, url },
41
- // so we provide a minimal stub to avoid runtime errors in prerender context.
42
- const req = event.node?.req
43
- ? {
44
- ...event.node.req,
45
- url: requestPath,
46
- originalUrl: requestPath,
47
- }
48
- : {
49
- headers: { host: 'localhost' },
50
- url: requestPath,
51
- originalUrl: requestPath,
52
- connection: {},
53
- };
54
- const res = event.node?.res;
55
- const fetch = createFetch({
56
- fetch: (resource, init) => {
57
- const url = resource instanceof Request ? resource.url : resource.toString();
58
- return fetchWithEvent(event, url, init);
59
- }
15
+ const html = await renderer(event.node.req.url, template, {
16
+ req: event.node.req,
17
+ res: event.node.res,
60
18
  });
61
19
 
62
- const html = await renderer(requestPath, template, { req, res, fetch });
63
-
64
20
  return html;
65
21
  });`;
66
- }
67
- /**
68
- * Client-only renderer virtual module content.
69
- *
70
- * Used when SSR is disabled — simply serves the static index.html template
71
- * for every route, letting the client-side Angular router handle navigation.
72
- */
73
- export function clientRenderer(templatePath) {
74
- return `
75
- import { readFileSync } from 'node:fs';
76
- import { defineHandler } from 'h3';
22
+ export const clientRenderer = `
23
+ import { eventHandler } from 'h3';
77
24
 
78
- const template = readFileSync(${JSON.stringify(templatePath)}, 'utf8');
25
+ // @ts-ignore
26
+ import template from '#analog/index';
79
27
 
80
- export default defineHandler(async (event) => {
81
- event.res.headers.set('content-type', 'text/html; charset=utf-8');
28
+ export default eventHandler(async () => {
82
29
  return template;
83
30
  });
84
31
  `;
85
- }
86
- /**
87
- * API middleware virtual module content.
88
- *
89
- * Intercepts requests matching the configured API prefix and either:
90
- * - Uses event-bound internal forwarding for GET requests (except .xml routes)
91
- * - Uses request proxying for all other methods to forward the full request
92
- *
93
- * h3 v2 idiomatic APIs used:
94
- * - defineHandler (replaces defineEventHandler / eventHandler)
95
- * - event.path (replaces event.node.req.url)
96
- * - event.method (replaces event.node.req.method)
97
- * - proxyRequest is retained internally because it preserves Nitro route
98
- * matching for event-bound server requests during SSR/prerender
99
- * - Object.fromEntries(event.req.headers.entries()) replaces direct event.node.req.headers access
100
- *
101
- * `fetchWithEvent` keeps the active event context while forwarding to a
102
- * rewritten path, which avoids falling through to the HTML renderer when
103
- * SSR code makes relative API requests.
104
- */
105
32
  export const apiMiddleware = `
106
- import { defineHandler, fetchWithEvent, proxyRequest } from 'h3';
107
- import { useRuntimeConfig } from 'nitro/runtime-config';
33
+ import { eventHandler, proxyRequest } from 'h3';
34
+ import { useRuntimeConfig } from '#imports';
108
35
 
109
- export default defineHandler(async (event) => {
36
+ export default eventHandler(async (event) => {
110
37
  const prefix = useRuntimeConfig().prefix;
111
38
  const apiPrefix = \`\${prefix}/\${useRuntimeConfig().apiPrefix}\`;
112
39
 
113
- if (event.path?.startsWith(apiPrefix)) {
114
- const reqUrl = event.path?.replace(apiPrefix, '');
40
+ if (event.node.req.url?.startsWith(apiPrefix)) {
41
+ const reqUrl = event.node.req.url?.replace(apiPrefix, '');
115
42
 
116
43
  if (
117
- event.method === 'GET' &&
44
+ event.node.req.method === 'GET' &&
118
45
  // in the case of XML routes, we want to proxy the request so that nitro gets the correct headers
119
46
  // and can render the XML correctly as a static asset
120
- !event.path?.endsWith('.xml')
47
+ !event.node.req.url?.endsWith('.xml')
121
48
  ) {
122
- return fetchWithEvent(event, reqUrl, {
123
- headers: Object.fromEntries(event.req.headers.entries()),
124
- });
49
+ return $fetch(reqUrl, { headers: event.node.req.headers });
125
50
  }
126
51
 
127
- return proxyRequest(event, reqUrl);
52
+ return proxyRequest(event, reqUrl, {
53
+ // @ts-ignore
54
+ fetch: $fetch.native,
55
+ });
128
56
  }
129
57
  });`;
130
58
  //# sourceMappingURL=renderers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"renderers.js","sourceRoot":"","sources":["../../../../../../packages/vite-plugin-nitro/src/lib/utils/renderers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CAAC,YAAoB;IAC9C,OAAO;;;;;;;gCAOuB,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA0CxD,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,YAAoB;IACjD,OAAO;;;;gCAIuB,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC;;;;;;CAM3D,CAAC;AACF,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;IAwBzB,CAAC"}
1
+ {"version":3,"file":"renderers.js","sourceRoot":"","sources":["../../../../../../packages/vite-plugin-nitro/src/lib/utils/renderers.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;IAoBvB,CAAC;AAEL,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;CAS7B,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;IAyBzB,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { NitroConfig } from 'nitro/types';
1
+ import { NitroConfig } from 'nitropack';
2
2
  import type { Plugin } from 'vite';
3
3
  import { Options } from './options.js';
4
4
  export declare function nitro(options?: Options, nitroOptions?: NitroConfig): Plugin[];
@@ -1,8 +1,9 @@
1
- import { build, createDevServer, createNitro } from 'nitro/builder';
2
- import * as vite from 'vite';
1
+ import { build, createDevServer, createNitro } from 'nitropack';
2
+ import { toNodeListener } from 'h3';
3
3
  import { mergeConfig, normalizePath } from 'vite';
4
- import { relative, resolve } from 'node:path';
5
- import { pathToFileURL } from 'node:url';
4
+ import { dirname, relative, resolve } from 'node:path';
5
+ import { platform } from 'node:os';
6
+ import { fileURLToPath, pathToFileURL } from 'node:url';
6
7
  import { existsSync, readFileSync } from 'node:fs';
7
8
  import { buildServer } from './build-server.js';
8
9
  import { buildSSRApp } from './build-ssr.js';
@@ -10,115 +11,13 @@ import { pageEndpointsPlugin } from './plugins/page-endpoints.js';
10
11
  import { getPageHandlers } from './utils/get-page-handlers.js';
11
12
  import { buildSitemap } from './build-sitemap.js';
12
13
  import { devServerPlugin } from './plugins/dev-server-plugin.js';
13
- import { toWebRequest, writeWebResponseToNode, } from './utils/node-web-bridge.js';
14
14
  import { getMatchingContentFilesWithFrontMatter } from './utils/get-content-files.js';
15
15
  import { ssrRenderer, clientRenderer, apiMiddleware, } from './utils/renderers.js';
16
+ const isWindows = platform() === 'win32';
17
+ const filePrefix = isWindows ? 'file:///' : '';
16
18
  let clientOutputPath = '';
17
- let rendererIndexEntry = '';
18
- function createNitroMiddlewareHandler(handler) {
19
- return {
20
- route: '/**',
21
- handler,
22
- middleware: true,
23
- };
24
- }
25
- function createRollupBeforeHook(externalEntries) {
26
- return (_nitro, bundlerConfig) => {
27
- removeInvalidRollupCodeSplitting(_nitro, bundlerConfig);
28
- if (externalEntries.length === 0) {
29
- return;
30
- }
31
- const existing = bundlerConfig.external;
32
- if (!existing) {
33
- bundlerConfig.external = externalEntries;
34
- }
35
- else if (typeof existing === 'function') {
36
- bundlerConfig.external = (source, importer, isResolved) => existing(source, importer, isResolved) ||
37
- externalEntries.includes(source);
38
- }
39
- else if (Array.isArray(existing)) {
40
- bundlerConfig.external = [...existing, ...externalEntries];
41
- }
42
- else {
43
- bundlerConfig.external = [existing, ...externalEntries];
44
- }
45
- };
46
- }
47
- function appendNoExternals(noExternals, ...entries) {
48
- if (!noExternals) {
49
- return entries;
50
- }
51
- return Array.isArray(noExternals)
52
- ? [...noExternals, ...entries]
53
- : noExternals;
54
- }
55
- function removeInvalidRollupCodeSplitting(_nitro, bundlerConfig) {
56
- // Workaround for a Nitro v3 alpha bundler bug:
57
- //
58
- // Analog does not add `output.codeSplitting` to Nitro's Rollup config, but
59
- // Nitro 3.0.1-alpha.2 builds an internal server bundler config that can
60
- // still contain that key while running under Vite 8 / Rolldown. At runtime
61
- // this surfaces as:
62
- //
63
- // Warning: Invalid output options (1 issue found)
64
- // - For the "codeSplitting". Invalid key: Expected never but received "codeSplitting".
65
- //
66
- // That warning comes from Nitro's own bundler handoff, not from user config
67
- // in Analog apps. We remove only the invalid `output.codeSplitting` field
68
- // right before Nitro starts prerender/server builds.
69
- //
70
- // Why this is safe:
71
- // - Analog is not relying on Nitro-side `output.codeSplitting`.
72
- // - The warning path only rejects the option; removing it restores the
73
- // default Nitro/Rollup behavior instead of changing any Analog semantics.
74
- // - The hook is narrowly scoped to the final Nitro bundler config, so it
75
- // does not affect the normal Vite client/SSR environment build config.
76
- const output = bundlerConfig['output'];
77
- if (!output || Array.isArray(output) || typeof output !== 'object') {
78
- return;
79
- }
80
- if ('codeSplitting' in output) {
81
- delete output['codeSplitting'];
82
- }
83
- // Nitro's default server bundler config currently enables manual chunking for
84
- // node_modules. Under Nitro v3 alpha + Rollup 4.59 this can crash during the
85
- // prerender rebundle with "Cannot read properties of undefined (reading
86
- // 'included')" while generating chunks. A single server bundle is acceptable
87
- // here, so strip manualChunks until the upstream bug is fixed.
88
- if ('manualChunks' in output) {
89
- delete output['manualChunks'];
90
- }
91
- }
92
- function resolveClientOutputPath(workspaceRoot, rootDir, configuredOutDir, ssrBuild) {
93
- if (clientOutputPath) {
94
- return clientOutputPath;
95
- }
96
- if (!ssrBuild) {
97
- return resolve(workspaceRoot, rootDir, configuredOutDir || 'dist/client');
98
- }
99
- // SSR builds write server assets to dist/<app>/ssr, but the renderer template
100
- // still needs the client index.html emitted to dist/<app>/client.
101
- return resolve(workspaceRoot, 'dist', rootDir, 'client');
102
- }
103
- function toNitroSsrEntrypointSpecifier(ssrEntryPath) {
104
- // Nitro rebundles the generated SSR entry. On Windows, a file URL preserves
105
- // the importer location so relative "./assets/*" imports resolve correctly.
106
- return process.platform === 'win32'
107
- ? pathToFileURL(ssrEntryPath).href
108
- : normalizePath(ssrEntryPath);
109
- }
110
- function resolveBuiltSsrEntryPath(ssrOutDir) {
111
- const candidatePaths = [
112
- resolve(ssrOutDir, 'main.server.mjs'),
113
- resolve(ssrOutDir, 'main.server.js'),
114
- resolve(ssrOutDir, 'main.server'),
115
- ];
116
- const ssrEntryPath = candidatePaths.find((candidatePath) => existsSync(candidatePath));
117
- if (!ssrEntryPath) {
118
- throw new Error(`Unable to locate the built SSR entry in "${ssrOutDir}". Expected one of: ${candidatePaths.join(', ')}`);
119
- }
120
- return ssrEntryPath;
121
- }
19
+ const __filename = fileURLToPath(import.meta.url);
20
+ const __dirname = dirname(__filename);
122
21
  export function nitro(options, nitroOptions) {
123
22
  const workspaceRoot = options?.workspaceRoot ?? process.cwd();
124
23
  const sourceRoot = options?.sourceRoot ?? 'src';
@@ -136,7 +35,6 @@ export function nitro(options, nitroOptions) {
136
35
  let nitroConfig;
137
36
  let environmentBuild = false;
138
37
  let hasAPIDir = false;
139
- const rollupExternalEntries = [];
140
38
  const routeSitemaps = {};
141
39
  const routeSourceFiles = {};
142
40
  let rootDir = workspaceRoot;
@@ -159,8 +57,7 @@ export function nitro(options, nitroOptions) {
159
57
  rootDir = relative(workspaceRoot, config.root || '.') || '.';
160
58
  hasAPIDir = existsSync(resolve(workspaceRoot, rootDir, `${sourceRoot}/server/routes/${options?.apiPrefix || 'api'}`));
161
59
  const buildPreset = process.env['BUILD_PRESET'] ??
162
- nitroOptions?.preset ??
163
- (process.env['VERCEL'] ? 'vercel' : undefined);
60
+ nitroOptions?.preset;
164
61
  const pageHandlers = getPageHandlers({
165
62
  workspaceRoot,
166
63
  sourceRoot,
@@ -168,14 +65,12 @@ export function nitro(options, nitroOptions) {
168
65
  additionalPagesDirs: options?.additionalPagesDirs,
169
66
  hasAPIDir,
170
67
  });
171
- const resolvedClientOutputPath = resolveClientOutputPath(workspaceRoot, rootDir, config.build?.outDir, ssrBuild);
172
- rendererIndexEntry = normalizePath(resolve(resolvedClientOutputPath, 'index.html'));
173
68
  nitroConfig = {
174
- rootDir: normalizePath(rootDir),
69
+ rootDir,
175
70
  preset: buildPreset,
176
71
  compatibilityDate: '2024-11-19',
177
72
  logLevel: nitroOptions?.logLevel || 0,
178
- serverDir: normalizePath(`${sourceRoot}/server`),
73
+ srcDir: normalizePath(`${sourceRoot}/server`),
179
74
  scanDirs: [
180
75
  normalizePath(`${rootDir}/${sourceRoot}/server`),
181
76
  ...(options?.additionalAPIDirs || []).map((dir) => normalizePath(`${workspaceRoot}${dir}`)),
@@ -192,15 +87,10 @@ export function nitro(options, nitroOptions) {
192
87
  apiPrefix: apiPrefix.substring(1),
193
88
  prefix,
194
89
  },
195
- // Analog provides its own renderer handler; prevent Nitro v3 from
196
- // auto-detecting index.html in rootDir and adding a conflicting one.
197
- renderer: false,
90
+ // Fixes support for Rolldown
198
91
  imports: {
199
92
  autoImport: false,
200
93
  },
201
- hooks: {
202
- 'rollup:before': createRollupBeforeHook(rollupExternalEntries),
203
- },
204
94
  rollupConfig: {
205
95
  onwarn(warning) {
206
96
  if (warning.message.includes('empty chunk') &&
@@ -214,7 +104,12 @@ export function nitro(options, nitroOptions) {
214
104
  ...(hasAPIDir
215
105
  ? []
216
106
  : useAPIMiddleware
217
- ? [createNitroMiddlewareHandler('#ANALOG_API_MIDDLEWARE')]
107
+ ? [
108
+ {
109
+ handler: '#ANALOG_API_MIDDLEWARE',
110
+ middleware: true,
111
+ },
112
+ ]
218
113
  : []),
219
114
  ...pageHandlers,
220
115
  ],
@@ -228,13 +123,13 @@ export function nitro(options, nitroOptions) {
228
123
  },
229
124
  },
230
125
  virtual: {
231
- '#ANALOG_SSR_RENDERER': ssrRenderer(rendererIndexEntry),
232
- '#ANALOG_CLIENT_RENDERER': clientRenderer(rendererIndexEntry),
126
+ '#ANALOG_SSR_RENDERER': ssrRenderer,
127
+ '#ANALOG_CLIENT_RENDERER': clientRenderer,
233
128
  ...(hasAPIDir ? {} : { '#ANALOG_API_MIDDLEWARE': apiMiddleware }),
234
129
  },
235
130
  };
236
131
  if (isVercelPreset(buildPreset)) {
237
- nitroConfig = withVercelOutputAPI(nitroConfig, workspaceRoot, buildPreset);
132
+ nitroConfig = withVercelOutputAPI(nitroConfig, workspaceRoot);
238
133
  }
239
134
  if (isCloudflarePreset(buildPreset)) {
240
135
  nitroConfig = withCloudflareOutput(nitroConfig);
@@ -249,30 +144,17 @@ export function nitro(options, nitroOptions) {
249
144
  }
250
145
  if (!ssrBuild && !isTest) {
251
146
  // store the client output path for the SSR build config
252
- clientOutputPath = resolvedClientOutputPath;
147
+ clientOutputPath = resolve(workspaceRoot, rootDir, config.build?.outDir || 'dist/client');
253
148
  }
149
+ const indexEntry = normalizePath(resolve(clientOutputPath, 'index.html'));
150
+ nitroConfig.alias = {
151
+ '#analog/index': indexEntry,
152
+ };
254
153
  if (isBuild) {
255
- nitroConfig.publicAssets = [
256
- { dir: normalizePath(clientOutputPath), maxAge: 0 },
257
- ];
258
- // In Nitro v3, renderer.entry is resolved via resolveModulePath()
259
- // during options normalization, which requires a real filesystem path.
260
- // Virtual modules (prefixed with #) can't survive this resolution.
261
- // Instead, we add the renderer as a catch-all handler directly —
262
- // this is functionally equivalent to what Nitro does internally
263
- // (it converts renderer.entry into a { route: '/**', lazy: true }
264
- // handler), but avoids the filesystem resolution step.
265
- const rendererHandler = options?.ssr
154
+ nitroConfig.publicAssets = [{ dir: clientOutputPath }];
155
+ nitroConfig.renderer = options?.ssr
266
156
  ? '#ANALOG_SSR_RENDERER'
267
157
  : '#ANALOG_CLIENT_RENDERER';
268
- nitroConfig.handlers = [
269
- ...(nitroConfig.handlers || []),
270
- {
271
- handler: rendererHandler,
272
- route: '/**',
273
- lazy: true,
274
- },
275
- ];
276
158
  if (isEmptyPrerenderRoutes(options)) {
277
159
  nitroConfig.prerender = {};
278
160
  nitroConfig.prerender.routes = ['/'];
@@ -338,26 +220,30 @@ export function nitro(options, nitroOptions) {
338
220
  }, []);
339
221
  }
340
222
  if (ssrBuild) {
341
- if (process.platform === 'win32') {
342
- nitroConfig.noExternals = appendNoExternals(nitroConfig.noExternals, 'std-env');
223
+ if (isWindows) {
224
+ nitroConfig.externals = {
225
+ inline: ['std-env'],
226
+ };
343
227
  }
344
- rollupExternalEntries.push('rxjs', 'node-fetch-native/dist/polyfill');
345
228
  nitroConfig = {
346
229
  ...nitroConfig,
230
+ externals: {
231
+ ...nitroConfig.externals,
232
+ external: ['rxjs', 'node-fetch-native/dist/polyfill'],
233
+ },
347
234
  moduleSideEffects: ['zone.js/node', 'zone.js/fesm2015/zone-node'],
348
235
  handlers: [
349
236
  ...(hasAPIDir
350
237
  ? []
351
238
  : useAPIMiddleware
352
- ? [createNitroMiddlewareHandler('#ANALOG_API_MIDDLEWARE')]
239
+ ? [
240
+ {
241
+ handler: '#ANALOG_API_MIDDLEWARE',
242
+ middleware: true,
243
+ },
244
+ ]
353
245
  : []),
354
246
  ...pageHandlers,
355
- // Preserve the renderer catch-all handler added above
356
- {
357
- handler: rendererHandler,
358
- route: '/**',
359
- lazy: true,
360
- },
361
247
  ],
362
248
  };
363
249
  }
@@ -374,7 +260,7 @@ export function nitro(options, nitroOptions) {
374
260
  ssr: {
375
261
  build: {
376
262
  ssr: true,
377
- [vite.rolldownVersion ? 'rolldownOptions' : 'rollupOptions']: {
263
+ rollupOptions: {
378
264
  input: options?.entryServer ||
379
265
  resolve(workspaceRoot, rootDir, `${sourceRoot}/main.server.ts`),
380
266
  },
@@ -392,16 +278,17 @@ export function nitro(options, nitroOptions) {
392
278
  builds.push(builder.build(builder.environments['ssr']));
393
279
  }
394
280
  await Promise.all(builds);
395
- const ssrOutDir = options?.ssrBuildDir ||
396
- resolve(workspaceRoot, 'dist', rootDir, `ssr`);
397
- if (options?.ssr || nitroConfig.prerender?.routes?.length) {
398
- const ssrEntryPath = resolveBuiltSsrEntryPath(ssrOutDir);
399
- const ssrEntry = toNitroSsrEntrypointSpecifier(ssrEntryPath);
400
- nitroConfig.alias = {
401
- ...nitroConfig.alias,
402
- '#analog/ssr': ssrEntry,
403
- };
281
+ let ssrEntryPath = resolve(options?.ssrBuildDir ||
282
+ resolve(workspaceRoot, 'dist', rootDir, `ssr`), `main.server${filePrefix ? '.js' : ''}`);
283
+ // add check for main.server.mjs fallback on Windows
284
+ if (isWindows && !existsSync(ssrEntryPath)) {
285
+ ssrEntryPath = ssrEntryPath.replace('.js', '.mjs');
404
286
  }
287
+ const ssrEntry = normalizePath(filePrefix + ssrEntryPath);
288
+ nitroConfig.alias = {
289
+ ...nitroConfig.alias,
290
+ '#analog/ssr': ssrEntry,
291
+ };
405
292
  await buildServer(options, nitroConfig, routeSourceFiles);
406
293
  if (nitroConfig.prerender?.routes?.length &&
407
294
  options?.prerender?.sitemap) {
@@ -422,33 +309,22 @@ export function nitro(options, nitroOptions) {
422
309
  if (isServe && !isTest) {
423
310
  const nitro = await createNitro({
424
311
  dev: true,
425
- // Nitro's Vite builder now rejects `build()` in dev mode, but Analog's
426
- // dev integration still relies on the builder-driven reload hooks.
427
- // Force the server worker onto Rollup for this dev-only path.
428
- builder: 'rollup',
429
312
  ...nitroConfig,
430
313
  });
431
314
  const server = createDevServer(nitro);
432
315
  await build(nitro);
433
- const apiHandler = async (req, res) => {
434
- // Nitro v3's dev server is fetch-first, so adapt Vite's Node
435
- // request once and let Nitro respond with a standard Web Response.
436
- const response = await server.fetch(toWebRequest(req));
437
- await writeWebResponseToNode(res, response);
438
- };
316
+ const apiHandler = toNodeListener(server.app);
439
317
  if (hasAPIDir) {
440
318
  viteServer.middlewares.use((req, res, next) => {
441
319
  if (req.url?.startsWith(`${prefix}${apiPrefix}`)) {
442
- void apiHandler(req, res).catch((error) => next(error));
320
+ apiHandler(req, res);
443
321
  return;
444
322
  }
445
323
  next();
446
324
  });
447
325
  }
448
326
  else {
449
- viteServer.middlewares.use(apiPrefix, (req, res, next) => {
450
- void apiHandler(req, res).catch((error) => next(error));
451
- });
327
+ viteServer.middlewares.use(apiPrefix, apiHandler);
452
328
  }
453
329
  viteServer.httpServer?.once('listening', () => {
454
330
  process.env['ANALOG_HOST'] = !viteServer.config.server.host
@@ -482,16 +358,17 @@ export function nitro(options, nitroOptions) {
482
358
  // sitemap needs to be built after all directories are built
483
359
  await buildSitemap(config, options.prerender.sitemap, nitroConfig.prerender.routes, clientOutputPath, routeSitemaps);
484
360
  }
485
- const closeBundleSsrOutDir = options?.ssrBuildDir ||
486
- resolve(workspaceRoot, 'dist', rootDir, `ssr`);
487
- if (options?.ssr || nitroConfig.prerender?.routes?.length) {
488
- const ssrEntryPath = resolveBuiltSsrEntryPath(closeBundleSsrOutDir);
489
- const ssrEntry = toNitroSsrEntrypointSpecifier(ssrEntryPath);
490
- nitroConfig.alias = {
491
- ...nitroConfig.alias,
492
- '#analog/ssr': ssrEntry,
493
- };
361
+ let ssrEntryPath = resolve(options?.ssrBuildDir ||
362
+ resolve(workspaceRoot, 'dist', rootDir, `ssr`), `main.server${filePrefix ? '.js' : ''}`);
363
+ // add check for main.server.mjs fallback on Windows
364
+ if (isWindows && !existsSync(ssrEntryPath)) {
365
+ ssrEntryPath = ssrEntryPath.replace('.js', '.mjs');
494
366
  }
367
+ const ssrEntry = normalizePath(filePrefix + ssrEntryPath);
368
+ nitroConfig.alias = {
369
+ ...nitroConfig.alias,
370
+ '#analog/ssr': ssrEntry,
371
+ };
495
372
  await buildServer(options, nitroConfig, routeSourceFiles);
496
373
  console.log(`\n\nThe '@analogjs/platform' server has been successfully built.`);
497
374
  }
@@ -520,24 +397,8 @@ function isArrayWithElements(arr) {
520
397
  }
521
398
  const isVercelPreset = (buildPreset) => process.env['VERCEL'] ||
522
399
  (buildPreset && buildPreset.toLowerCase().includes('vercel'));
523
- const withVercelOutputAPI = (nitroConfig, workspaceRoot, buildPreset) => ({
400
+ const withVercelOutputAPI = (nitroConfig, workspaceRoot) => ({
524
401
  ...nitroConfig,
525
- preset: nitroConfig?.preset ??
526
- (buildPreset?.toLowerCase().includes('vercel-edge')
527
- ? 'vercel-edge'
528
- : 'vercel'),
529
- vercel: {
530
- ...nitroConfig?.vercel,
531
- ...(buildPreset?.toLowerCase().includes('vercel-edge')
532
- ? {}
533
- : {
534
- entryFormat: nitroConfig?.vercel?.entryFormat ?? 'node',
535
- functions: {
536
- runtime: nitroConfig?.vercel?.functions?.runtime ?? 'nodejs24.x',
537
- ...nitroConfig?.vercel?.functions,
538
- },
539
- }),
540
- },
541
402
  output: {
542
403
  ...nitroConfig?.output,
543
404
  dir: normalizePath(resolve(workspaceRoot, '.vercel', 'output')),