@nuxt/nitro-server 4.3.0 → 4.4.2

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.
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
- [![Nuxt banner](https://github.com/nuxt/nuxt/blob/main/.github/assets/banner.svg)](https://nuxt.com)
1
+ <a href="https://nuxt.com"><img width="830" height="213" src="https://github.com/nuxt/nuxt/blob/main/.github/assets/banner.svg" alt="Nuxt banner"></a>
2
2
 
3
3
  # Nuxt
4
4
 
5
5
  <p>
6
- <a href="https://www.npmjs.com/package/nuxt"><img src="https://img.shields.io/npm/v/nuxt.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="Version"></a>
7
- <a href="https://www.npmjs.com/package/nuxt"><img src="https://img.shields.io/npm/dm/nuxt.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="Downloads"></a>
6
+ <a href="https://npmx.dev/package/nuxt"><img src="https://npmx.dev/api/registry/badge/version/nuxt" alt="Version"></a>
7
+ <a href="https://npmx.dev/package/nuxt"><img src="https://npmx.dev/api/registry/badge/downloads/nuxt" alt="Downloads"></a>
8
8
  <a href="https://github.com/nuxt/nuxt/blob/main/LICENSE"><img src="https://img.shields.io/github/license/nuxt/nuxt.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="License"></a>
9
9
  <a href="https://nuxt.com/modules"><img src="https://img.shields.io/badge/dynamic/json?url=https://nuxt.com/api/v1/modules&query=$.stats.modules&label=Modules&style=flat&colorA=18181B&colorB=28CF8D" alt="Modules"></a>
10
10
  <a href="https://nuxt.com"><img src="https://img.shields.io/badge/Nuxt%20Docs-18181B?logo=nuxt" alt="Website"></a>
11
11
  <a href="https://chat.nuxt.dev"><img src="https://img.shields.io/badge/Nuxt%20Discord-18181B?logo=discord" alt="Discord"></a>
12
- <a href="https://securityscorecards.dev/"><img src="https://api.securityscorecards.dev/projects/github.com/nuxt/nuxt/badge" alt="Nuxt openssf scorecard score"></a>
12
+ <a href="https://securityscorecards.dev/viewer/?uri=github.com/nuxt/nuxt"><img src="https://api.securityscorecards.dev/projects/github.com/nuxt/nuxt/badge" alt="Nuxt openssf scorecard score"></a>
13
13
  <a href="https://deepwiki.com/nuxt/nuxt"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
14
14
  </p>
15
15
 
@@ -111,7 +111,7 @@ Follow the docs to [Set Up Your Local Development Environment](https://nuxt.com/
111
111
  ## <a name="follow-us">🔗 Follow Us</a>
112
112
 
113
113
  <p valign="center">
114
- <a href="https://go.nuxt.com/discord"><img width="20px" src="https://github.com/nuxt/nuxt/blob/main/.github/assets/discord.svg" alt="Discord"></a>&nbsp;&nbsp;<a href="https://go.nuxt.com/x"><img width="20px" src="https://github.com/nuxt/nuxt/blob/main/.github/assets/twitter.svg" alt="Twitter"></a>&nbsp;&nbsp;<a href="https://go.nuxt.com/github"><img width="20px" src="https://github.com/nuxt/nuxt/blob/main/.github/assets/github.svg" alt="GitHub"></a>&nbsp;&nbsp;<a href="https://go.nuxt.com/bluesky"><img width="20px" src="https://github.com/nuxt/nuxt/blob/main/.github/assets/bluesky.svg" alt="Bluesky"></a>
114
+ <a href="https://go.nuxt.com/discord"><img width="20" src="https://github.com/nuxt/nuxt/blob/main/.github/assets/discord.svg" alt="Discord"></a>&nbsp;&nbsp;<a href="https://go.nuxt.com/x"><img width="20" src="https://github.com/nuxt/nuxt/blob/main/.github/assets/twitter.svg" alt="Twitter"></a>&nbsp;&nbsp;<a href="https://go.nuxt.com/github"><img width="20" src="https://github.com/nuxt/nuxt/blob/main/.github/assets/github.svg" alt="GitHub"></a>&nbsp;&nbsp;<a href="https://go.nuxt.com/bluesky"><img width="20" src="https://github.com/nuxt/nuxt/blob/main/.github/assets/bluesky.svg" alt="Bluesky"></a>
115
115
  </p>
116
116
 
117
117
  ## <a name="license">⚖️ License</a>
@@ -0,0 +1,11 @@
1
+ # Licenses of Bundled Dependencies
2
+
3
+ The published artifact additionally contains code with the following licenses:
4
+ MIT
5
+
6
+ # Bundled Dependencies
7
+
8
+ ## nuxt
9
+
10
+ License: MIT
11
+ Repository: https://github.com/nuxt/nuxt
package/dist/index.d.mts CHANGED
@@ -150,7 +150,7 @@ declare module "@nuxt/schema" {
150
150
  * @example
151
151
  * ```js
152
152
  * serverHandlers: [
153
- * { route: '/path/foo/**:name', handler: '~/server/foohandler.ts' }
153
+ * { route: '/path/foo/**:name', handler: '#server/foohandler.ts' }
154
154
  * ]
155
155
  * ```
156
156
  */
package/dist/index.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { performance } from "node:perf_hooks";
1
2
  import { fileURLToPath, pathToFileURL } from "node:url";
2
3
  import { existsSync, promises, readFileSync } from "node:fs";
3
4
  import { cpus } from "node:os";
@@ -15,14 +16,13 @@ import { addPlugin, addTemplate, addVitePlugin, createIsIgnored, findPath, getDi
15
16
  import escapeRE from "escape-string-regexp";
16
17
  import { defu } from "defu";
17
18
  import { defineEventHandler, dynamicEventHandler, handleCors, setHeader } from "h3";
18
- import { isWindows } from "std-env";
19
+ import { addDependency } from "nypm";
20
+ import { hasTTY, isCI, isWindows } from "std-env";
19
21
  import { ImpoundPlugin } from "impound";
20
22
  import { resolveModulePath } from "exsolve";
21
23
  import { runtimeDependencies } from "nitropack/runtime/meta";
22
-
23
24
  //#region package.json
24
- var version = "4.3.0";
25
-
25
+ var version = "4.4.2";
26
26
  //#endregion
27
27
  //#region src/utils.ts
28
28
  function toArray(value) {
@@ -31,32 +31,63 @@ function toArray(value) {
31
31
  let _distDir = dirname(fileURLToPath(import.meta.url));
32
32
  if (/(?:chunks|shared)$/.test(_distDir)) _distDir = dirname(_distDir);
33
33
  const distDir = _distDir;
34
-
35
34
  //#endregion
36
35
  //#region ../ui-templates/dist/templates/spa-loading-icon.ts
37
36
  const template = () => {
38
37
  return "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"80\" fill=\"none\" class=\"nuxt-spa-loading\" viewBox=\"0 0 37 25\"><path d=\"M24.236 22.006h10.742L25.563 5.822l-8.979 14.31a4 4 0 0 1-3.388 1.874H2.978l11.631-20 5.897 10.567\"/></svg><style>.nuxt-spa-loading{left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}.nuxt-spa-loading>path{animation:nuxt-spa-loading-move 3s linear infinite;fill:none;stroke:#00dc82;stroke-dasharray:128;stroke-dashoffset:128;stroke-linecap:round;stroke-linejoin:round;stroke-width:4px}@keyframes nuxt-spa-loading-move{to{stroke-dashoffset:-128}}</style>";
39
38
  };
40
-
41
39
  //#endregion
42
40
  //#region ../nuxt/src/core/plugins/import-protection.ts
43
41
  function createImportProtectionPatterns(nuxt, options) {
44
42
  const patterns = [];
45
43
  const context = contextFlags[options.context];
46
- patterns.push([/^(nuxt|nuxt3|nuxt-nightly)$/, `\`nuxt\`, or \`nuxt-nightly\` cannot be imported directly in ${context}.` + (options.context === "nuxt-app" ? " Instead, import runtime Nuxt composables from `#app` or `#imports`." : "")]);
47
- patterns.push([/^((~|~~|@|@@)?\/)?nuxt\.config(\.|$)/, "Importing directly from a `nuxt.config` file is not allowed. Instead, use runtime config or a module."]);
44
+ patterns.push([
45
+ /^(nuxt|nuxt3|nuxt-nightly)$/,
46
+ `\`nuxt\` or \`nuxt-nightly\` cannot be imported directly in ${context}.`,
47
+ options.context === "nuxt-app" ? ["Import runtime Nuxt composables from `#app` or `#imports` instead."] : ["Use `#app` or `#imports` for runtime composables in your Vue app code."]
48
+ ]);
49
+ patterns.push([
50
+ /^((~|~~|@|@@)?\/)?nuxt\.config(\.|$)/,
51
+ "Importing directly from a `nuxt.config` file is not allowed.",
52
+ [
53
+ "Use `useRuntimeConfig()` to access runtime config in your app.",
54
+ "Use `useAppConfig()` to access config that doesn't need to be changed at runtime.",
55
+ "Use a Nuxt module to access build-time configuration."
56
+ ]
57
+ ]);
48
58
  patterns.push([/(^|node_modules\/)@vue\/composition-api/]);
49
- for (const mod of nuxt.options._installedModules) if (mod.entryPath) patterns.push([/* @__PURE__ */ new RegExp(`^${escapeRE(mod.entryPath)}$`), "Importing directly from module entry-points is not allowed."]);
59
+ for (const mod of nuxt.options._installedModules) if (mod.entryPath) patterns.push([
60
+ new RegExp(`^${escapeRE(mod.entryPath)}$`),
61
+ "Importing directly from module entry-points is not allowed.",
62
+ ["Import from the module's runtime directory instead (e.g. `my-module/runtime/...`)."]
63
+ ]);
50
64
  for (const i of [
51
65
  /(^|node_modules\/)@nuxt\/(cli|kit|test-utils)/,
52
66
  /(^|node_modules\/)nuxi/,
53
67
  /(^|node_modules\/)nitro(?:pack)?(?:-nightly)?(?:$|\/)(?!(?:dist\/)?(?:node_modules|presets|runtime|types))/,
54
68
  /(^|node_modules\/)nuxt\/(config|kit|schema)/
55
- ]) patterns.push([i, `This module cannot be imported in ${context}.`]);
56
- if (options.context === "nitro-app" || options.context === "shared") for (const i of ["#app", /^#build(\/|$)/]) patterns.push([i, `Vue app aliases are not allowed in ${context}.`]);
69
+ ]) patterns.push([
70
+ i,
71
+ `This module cannot be imported in ${context}.`,
72
+ ["These are build-time only packages and cannot be used at runtime."]
73
+ ]);
74
+ if (options.context === "nitro-app" || options.context === "shared") for (const i of ["#app", /^#build(\/|$)/]) patterns.push([
75
+ i,
76
+ `Vue app aliases are not allowed in ${context}.`,
77
+ ["Move this code to your Vue app directory or use a shared utility."]
78
+ ]);
57
79
  if (options.context === "nuxt-app" || options.context === "shared") {
58
- patterns.push([/* @__PURE__ */ new RegExp(escapeRE(relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, nuxt.options.serverDir || "server"))) + "\\/(api|routes|middleware|plugins)\\/"), `Importing from server is not allowed in ${context}.`]);
59
- patterns.push([/^#server(\/|$)/, `Server aliases are not allowed in ${context}.`]);
80
+ const serverRelative = escapeRE(relative(nuxt.options.rootDir, resolve(nuxt.options.srcDir, nuxt.options.serverDir || "server")));
81
+ patterns.push([
82
+ new RegExp("^" + serverRelative + "\\/(api|routes|middleware|plugins)\\/"),
83
+ `Importing from server is not allowed in ${context}.`,
84
+ ["Use `$fetch()` or `useFetch()` to fetch data from server routes.", "Move shared logic to the `shared/` directory."]
85
+ ]);
86
+ patterns.push([
87
+ /^#server(\/|$)/,
88
+ `Server aliases are not allowed in ${context}.`,
89
+ ["Use `$fetch()` or `useFetch()` to call server endpoints.", "Move shared logic to the `shared/` directory."]
90
+ ]);
60
91
  }
61
92
  return patterns;
62
93
  }
@@ -65,7 +96,6 @@ const contextFlags = {
65
96
  "nuxt-app": "the Vue part of your app",
66
97
  "shared": "the #shared directory"
67
98
  };
68
-
69
99
  //#endregion
70
100
  //#region src/templates.ts
71
101
  const nitroSchemaTemplate = {
@@ -77,12 +107,9 @@ const nitroSchemaTemplate = {
77
107
  references,
78
108
  declarations
79
109
  });
80
- const sourceDir = join(nuxt.options.buildDir, "types");
110
+ const typesDir = join(nuxt.options.buildDir, "types");
81
111
  return `
82
- ${[...references.map((ref) => {
83
- if ("path" in ref && isAbsolute(ref.path)) ref.path = relative(sourceDir, ref.path);
84
- return `/// <reference ${renderAttrs(ref)} />`;
85
- }), ...declarations].join("\n")}
112
+ ${[...references.map((ref) => renderReference(ref, typesDir)), ...declarations].join("\n")}
86
113
 
87
114
  import type { RuntimeConfig } from 'nuxt/schema'
88
115
  import type { H3Event } from 'h3'
@@ -144,15 +171,9 @@ declare module 'nitropack/types' {
144
171
  `;
145
172
  }
146
173
  };
147
- function renderAttr(key, value) {
148
- return value ? `${key}="${value}"` : "";
149
- }
150
- function renderAttrs(obj) {
151
- const attrs = [];
152
- for (const key in obj) attrs.push(renderAttr(key, obj[key]));
153
- return attrs.join(" ");
174
+ function renderReference(ref, baseDir) {
175
+ return `/// <reference ${"path" in ref ? `path="${isAbsolute(ref.path) ? relative(baseDir, ref.path) : ref.path}"` : `types="${ref.types}"`} />`;
154
176
  }
155
-
156
177
  //#endregion
157
178
  //#region src/index.ts
158
179
  const logLevelMapReverse = {
@@ -171,7 +192,7 @@ async function bundle(nuxt) {
171
192
  }
172
193
  const layerPublicAssetsDirs = [];
173
194
  for (const dirs of layerDirs) if (existsSync(dirs.public)) layerPublicAssetsDirs.push({ dir: dirs.public });
174
- const excludePattern = excludePaths.length ? [/* @__PURE__ */ new RegExp(`node_modules\\/(?!${excludePaths.join("|")})`)] : [/node_modules/];
195
+ const excludePattern = excludePaths.length ? [new RegExp(`node_modules\\/(?!${excludePaths.join("|")})`)] : [/node_modules/];
175
196
  const rootDirWithSlash = withTrailingSlash(nuxt.options.rootDir);
176
197
  const moduleEntryPaths = [];
177
198
  for (const m of nuxt.options._installedModules) {
@@ -294,7 +315,8 @@ async function bundle(nuxt) {
294
315
  `export const NUXT_JSON_PAYLOADS = ${!!nuxt.options.experimental.renderJsonPayloads}`,
295
316
  `export const NUXT_ASYNC_CONTEXT = ${!!nuxt.options.experimental.asyncContext}`,
296
317
  `export const NUXT_SHARED_DATA = ${!!nuxt.options.experimental.sharedPrerenderData}`,
297
- `export const NUXT_PAYLOAD_EXTRACTION = ${!!nuxt.options.experimental.payloadExtraction}`,
318
+ `export const NUXT_PAYLOAD_EXTRACTION = ${nuxt.options.experimental.payloadExtraction !== false}`,
319
+ `export const NUXT_PAYLOAD_INLINE = ${nuxt.options.experimental.payloadExtraction !== true}`,
298
320
  `export const NUXT_RUNTIME_PAYLOAD_EXTRACTION = ${hasCachedRoutes}`
299
321
  ].join("\n");
300
322
  }
@@ -399,7 +421,8 @@ async function bundle(nuxt) {
399
421
  "appLayout",
400
422
  "cache",
401
423
  "isr",
402
- "swr"
424
+ "swr",
425
+ "ssr"
403
426
  ];
404
427
  function getRouteRulesRouter() {
405
428
  const routeRulesRouter = createRouter();
@@ -448,18 +471,19 @@ async function bundle(nuxt) {
448
471
  }
449
472
  });
450
473
  if (nuxt.options.experimental.payloadExtraction) {
451
- if (nuxt.options.dev) nuxt.hook("nitro:config", (nitroConfig$1) => {
452
- nitroConfig$1.prerender ||= {};
453
- nitroConfig$1.prerender.routes ||= [];
454
- nitroConfig$1.routeRules ||= {};
455
- for (const route of nitroConfig$1.prerender.routes) {
474
+ if (nuxt.options.dev) nuxt.hook("nitro:config", (nitroConfig) => {
475
+ nitroConfig.prerender ||= {};
476
+ nitroConfig.prerender.routes ||= [];
477
+ nitroConfig.routeRules ||= {};
478
+ for (const route of nitroConfig.prerender.routes) {
456
479
  if (!route) continue;
457
- nitroConfig$1.routeRules[route] = defu(nitroConfig$1.routeRules[route], { prerender: true });
480
+ nitroConfig.routeRules[route] = defu(nitroConfig.routeRules[route], { prerender: true });
458
481
  }
459
482
  });
460
- nuxt.hook("nitro:init", (nitro$1) => {
461
- nitro$1.hooks.hook("build:before", (nitro$2) => {
462
- for (const [route, value] of Object.entries(nitro$2.options.routeRules)) if (!route.endsWith("*") && !route.endsWith("/_payload.json")) {
483
+ nuxt.hook("nitro:init", (nitro) => {
484
+ nitro.hooks.hook("build:before", (nitro) => {
485
+ for (const [route, value] of Object.entries(nitro.options.routeRules)) if (!route.endsWith("*") && !route.endsWith("/_payload.json")) {
486
+ if (value.ssr === false) continue;
463
487
  if (value.isr || value.cache || value.prerender && nuxt.options.dev) {
464
488
  const payloadKey = route + "/_payload.json";
465
489
  const defaults = {};
@@ -468,7 +492,7 @@ async function bundle(nuxt) {
468
492
  "cache",
469
493
  ...nuxt.options.dev ? ["prerender"] : []
470
494
  ]) if (key in value) defaults[key] = value[key];
471
- nitro$2.options.routeRules[payloadKey] = defu(nitro$2.options.routeRules[payloadKey], defaults);
495
+ nitro.options.routeRules[payloadKey] = defu(nitro.options.routeRules[payloadKey], defaults);
472
496
  }
473
497
  }
474
498
  });
@@ -500,13 +524,13 @@ async function bundle(nuxt) {
500
524
  config.alias ||= {};
501
525
  config.alias["#app-manifest"] = join(tempDir, `meta/${buildId}.json`);
502
526
  });
503
- nuxt.hook("nitro:init", (nitro$1) => {
504
- nitro$1.hooks.hook("rollup:before", async (nitro$2) => {
527
+ nuxt.hook("nitro:init", (nitro) => {
528
+ nitro.hooks.hook("rollup:before", async (nitro) => {
505
529
  const prerenderedRoutes = /* @__PURE__ */ new Set();
506
530
  const routeRulesMatcher = getRouteRulesRouter();
507
- if (nitro$2._prerenderedRoutes?.length) {
531
+ if (nitro._prerenderedRoutes?.length) {
508
532
  const payloadSuffix = nuxt.options.experimental.renderJsonPayloads ? "/_payload.json" : "/_payload.js";
509
- for (const route of nitro$2._prerenderedRoutes) if (!route.error && route.route.endsWith(payloadSuffix)) {
533
+ for (const route of nitro._prerenderedRoutes) if (!route.error && route.route.endsWith(payloadSuffix)) {
510
534
  const url = route.route.slice(0, -payloadSuffix.length) || "/";
511
535
  if (!defu({}, ...findAllRoutes(routeRulesMatcher, void 0, url).reverse()).prerender) prerenderedRoutes.add(url);
512
536
  }
@@ -535,21 +559,72 @@ async function bundle(nuxt) {
535
559
  nitroConfig.virtual["#build/dist/server/styles.mjs"] = "export default {}";
536
560
  if (process.platform === "win32") nitroConfig.virtual["#build/dist/server/styles.mjs".replace(FORWARD_SLASH_RE, "\\")] = "export default {}";
537
561
  }
562
+ if (nuxt.options.experimental.decorators) {
563
+ const nitroDecoratorDeps = ["@rollup/plugin-babel", "@babel/plugin-proposal-decorators"];
564
+ let hasDeps = true;
565
+ for (const pkg of nitroDecoratorDeps) try {
566
+ await import(pkg);
567
+ } catch (_err) {
568
+ const err = _err;
569
+ if (err.code !== "ERR_MODULE_NOT_FOUND" && err.code !== "MODULE_NOT_FOUND") throw err;
570
+ if (!isCI && hasTTY) {
571
+ logger.info("Decorator support requires additional dependencies.");
572
+ if (await logger.prompt(`Install \`${nitroDecoratorDeps.join("` and `")}\`?`, {
573
+ type: "confirm",
574
+ initial: true
575
+ })) {
576
+ logger.start(`Installing ${nitroDecoratorDeps.map((d) => `\`${d}\``).join(" and ")}...`);
577
+ await addDependency(nitroDecoratorDeps, {
578
+ dev: true,
579
+ cwd: nuxt.options.rootDir,
580
+ silent: true
581
+ });
582
+ logger.info("Rerun Nuxt to enable decorator support.");
583
+ process.exit(1);
584
+ }
585
+ }
586
+ logger.warn(`Cannot find \`${pkg}\`. Install \`${nitroDecoratorDeps.join("` and `")}\` to enable decorator support.`);
587
+ hasDeps = false;
588
+ break;
589
+ }
590
+ if (hasDeps) {
591
+ const { babel } = await import("@rollup/plugin-babel");
592
+ nitroConfig.rollupConfig.plugins = toArray(await nitroConfig.rollupConfig.plugins || []);
593
+ nitroConfig.rollupConfig.plugins.unshift(babel({
594
+ babelHelpers: "bundled",
595
+ configFile: false,
596
+ extensions: [
597
+ ".ts",
598
+ ".js",
599
+ ".mjs",
600
+ ".mts"
601
+ ],
602
+ plugins: [["@babel/plugin-syntax-typescript", { isTSX: false }], ["@babel/plugin-proposal-decorators", { version: "2023-11" }]]
603
+ }), babel({
604
+ babelHelpers: "bundled",
605
+ configFile: false,
606
+ extensions: [".tsx", ".jsx"],
607
+ plugins: [["@babel/plugin-syntax-typescript", { isTSX: true }], ["@babel/plugin-proposal-decorators", { version: "2023-11" }]]
608
+ }));
609
+ }
610
+ }
538
611
  nitroConfig.rollupConfig.plugins = await nitroConfig.rollupConfig.plugins || [];
539
612
  nitroConfig.rollupConfig.plugins = toArray(nitroConfig.rollupConfig.plugins);
540
613
  const sharedDir = withTrailingSlash(resolve(nuxt.options.rootDir, nuxt.options.dir.shared));
541
614
  const relativeSharedDir = withTrailingSlash(relative(nuxt.options.rootDir, resolve(nuxt.options.rootDir, nuxt.options.dir.shared)));
542
615
  const sharedPatterns = [
543
616
  /^#shared\//,
544
- /* @__PURE__ */ new RegExp("^" + escapeRE(sharedDir)),
545
- /* @__PURE__ */ new RegExp("^" + escapeRE(relativeSharedDir))
617
+ new RegExp("^" + escapeRE(sharedDir)),
618
+ new RegExp("^" + escapeRE(relativeSharedDir))
546
619
  ];
547
620
  nitroConfig.rollupConfig.plugins.push(ImpoundPlugin.rollup({
548
621
  cwd: nuxt.options.rootDir,
622
+ trace: true,
549
623
  include: sharedPatterns,
550
624
  patterns: createImportProtectionPatterns(nuxt, { context: "shared" })
551
625
  }), ImpoundPlugin.rollup({
552
626
  cwd: nuxt.options.rootDir,
627
+ trace: true,
553
628
  patterns: createImportProtectionPatterns(nuxt, { context: "nitro-app" }),
554
629
  exclude: [/node_modules[\\/]nitro(?:pack)?(?:-nightly)?[\\/]|(packages|@nuxt)[\\/]nitro-server(?:-nightly)?[\\/](src|dist)[\\/]runtime[\\/]/, ...sharedPatterns]
555
630
  }));
@@ -600,10 +675,12 @@ async function bundle(nuxt) {
600
675
  tsConfig.compilerOptions.paths[alias] = [absolutePath];
601
676
  if (isDirectory) tsConfig.compilerOptions.paths[`${alias}/*`] = [`${absolutePath}/*`];
602
677
  }
678
+ nuxt._perf?.startPhase("nitro:createNitro");
603
679
  const nitro = await createNitro(nitroConfig, {
604
680
  compatibilityDate: nuxt.options.compatibilityDate,
605
681
  dotenv: nuxt.options._loadOptions?.dotenv
606
682
  });
683
+ nuxt._perf?.endPhase("nitro:createNitro");
607
684
  if (nuxt.options.experimental.serverAppConfig === false && nitro.options.imports) {
608
685
  nitro.options.imports.presets ||= [];
609
686
  nitro.options.imports.presets = nitro.options.imports.presets.map((preset) => typeof preset === "string" || !("imports" in preset) ? preset : {
@@ -611,10 +688,7 @@ async function bundle(nuxt) {
611
688
  imports: preset.imports.filter((i) => i !== "useAppConfig")
612
689
  });
613
690
  }
614
- if (nitro.options.static && nuxt.options.experimental.payloadExtraction === void 0) {
615
- logger.warn("Using experimental payload extraction for full-static output. You can opt-out by setting `experimental.payloadExtraction` to `false`.");
616
- nuxt.options.experimental.payloadExtraction = true;
617
- }
691
+ if (nitro.options.static && nuxt.options.experimental.payloadExtraction === false) logger.warn("Payload extraction is recommended for full-static output. You can enable it by setting `experimental.payloadExtraction` to `true` or `'client'`.");
618
692
  const spaLoadingTemplateFilePath = await spaLoadingTemplatePath(nuxt);
619
693
  nuxt.hook("builder:watch", async (_event, relativePath) => {
620
694
  if (resolve(nuxt.options.srcDir, relativePath) === spaLoadingTemplateFilePath) await nitro.hooks.callHook("rollup:reload");
@@ -631,8 +705,52 @@ async function bundle(nuxt) {
631
705
  } });
632
706
  nuxt._nitro = nitro;
633
707
  await nuxt.callHook("nitro:init", nitro);
708
+ if (nuxt._perf) nitro.hooks.hook("rollup:before", (_nitro, rollupConfig) => {
709
+ const plugins = rollupConfig.plugins || [];
710
+ for (const plugin of plugins) {
711
+ if (!plugin || !plugin.name) continue;
712
+ const pluginName = `nitro:${plugin.name}`;
713
+ for (const hookName of [
714
+ "transform",
715
+ "resolveId",
716
+ "load"
717
+ ]) {
718
+ const original = plugin[hookName];
719
+ if (typeof original !== "function") continue;
720
+ plugin[hookName] = function(...args) {
721
+ const start = performance.now();
722
+ const record = () => nuxt._perf?.recordBundlerPluginHook(pluginName, hookName, performance.now() - start, start);
723
+ try {
724
+ const result = original.apply(this, args);
725
+ if (result && typeof result === "object" && "then" in result) return result.finally(record);
726
+ record();
727
+ return result;
728
+ } catch (err) {
729
+ record();
730
+ throw err;
731
+ }
732
+ };
733
+ }
734
+ }
735
+ });
634
736
  nuxt["~runtimeDependencies"] ||= [];
635
737
  nuxt["~runtimeDependencies"].push(...runtimeDependencies, "unhead", "@unhead/vue", "@nuxt/devalue", "unstorage", ...nitro.options.inlineDynamicImports ? ["vue", "@vue/server-renderer"] : []);
738
+ addVitePlugin({
739
+ name: "nuxt:nitro:ssr-conditions",
740
+ configEnvironment(name, config) {
741
+ if (name === "ssr") {
742
+ config.resolve ||= {};
743
+ config.resolve.conditions = [...nitro.options.exportConditions || [], "import"];
744
+ }
745
+ }
746
+ });
747
+ addVitePlugin({
748
+ name: "nuxt:nitro:vue-feature-flags",
749
+ applyToEnvironment: (environment) => environment.name === "ssr" && environment.config.isProduction,
750
+ configResolved(config) {
751
+ for (const key in config.define) if (key.startsWith("__VUE")) nitro.options.replace[key] = config.define[key];
752
+ }
753
+ });
636
754
  nitro.vfs = nuxt.vfs = nitro.vfs || nuxt.vfs || {};
637
755
  nuxt.hook("close", () => nitro.hooks.callHook("close"));
638
756
  nitro.hooks.hook("prerender:routes", (routes) => {
@@ -666,12 +784,12 @@ async function bundle(nuxt) {
666
784
  handler: resolve(distDir, "runtime/handlers/renderer")
667
785
  });
668
786
  if (nuxt.options.experimental.chromeDevtoolsProjectSettings) {
669
- const cacheDir$1 = resolve(nuxt.options.rootDir, "node_modules/.cache/nuxt");
670
- let projectConfiguration = await readFile(join(cacheDir$1, "chrome-workspace.json"), "utf-8").then((r) => JSON.parse(r)).catch(() => null);
787
+ const cacheDir = resolve(nuxt.options.rootDir, "node_modules/.cache/nuxt");
788
+ let projectConfiguration = await readFile(join(cacheDir, "chrome-workspace.json"), "utf-8").then((r) => JSON.parse(r)).catch(() => null);
671
789
  if (!projectConfiguration) {
672
790
  projectConfiguration = { uuid: randomUUID() };
673
- await mkdir(cacheDir$1, { recursive: true });
674
- await writeFile(join(cacheDir$1, "chrome-workspace.json"), JSON.stringify(projectConfiguration), "utf-8");
791
+ await mkdir(cacheDir, { recursive: true });
792
+ await writeFile(join(cacheDir, "chrome-workspace.json"), JSON.stringify(projectConfiguration), "utf-8");
675
793
  }
676
794
  nitro.options.devHandlers.push({
677
795
  route: "/.well-known/appspecific/com.chrome.devtools.json",
@@ -681,14 +799,14 @@ async function bundle(nuxt) {
681
799
  } }))
682
800
  });
683
801
  }
684
- if (!nuxt.options.dev && nuxt.options.experimental.noVueServer) nitro.hooks.hook("rollup:before", (nitro$1) => {
685
- if (nitro$1.options.preset === "nitro-prerender") {
686
- nitro$1.options.errorHandler = resolve(distDir, "runtime/handlers/error");
802
+ if (!nuxt.options.dev && nuxt.options.experimental.noVueServer) nitro.hooks.hook("rollup:before", (nitro) => {
803
+ if (nitro.options.preset === "nitro-prerender") {
804
+ nitro.options.errorHandler = resolve(distDir, "runtime/handlers/error");
687
805
  return;
688
806
  }
689
- const nuxtErrorHandler = nitro$1.options.handlers.findIndex((h) => h.route === "/__nuxt_error");
690
- if (nuxtErrorHandler >= 0) nitro$1.options.handlers.splice(nuxtErrorHandler, 1);
691
- nitro$1.options.renderer = void 0;
807
+ const nuxtErrorHandler = nitro.options.handlers.findIndex((h) => h.route === "/__nuxt_error");
808
+ if (nuxtErrorHandler >= 0) nitro.options.handlers.splice(nuxtErrorHandler, 1);
809
+ nitro.options.renderer = void 0;
692
810
  });
693
811
  nitro.hooks.hook("types:extend", (types) => {
694
812
  types.tsConfig ||= {};
@@ -716,26 +834,17 @@ async function bundle(nuxt) {
716
834
  for (const route of ["/200.html", "/404.html"]) routes.add(route);
717
835
  if (!nuxt.options.ssr) routes.add("/index.html");
718
836
  });
719
- if (!nuxt.options.dev) nitro.hooks.hook("rollup:before", async (nitro$1) => {
720
- await copyPublicAssets(nitro$1);
721
- await nuxt.callHook("nitro:build:public-assets", nitro$1);
837
+ if (!nuxt.options.dev) nitro.hooks.hook("rollup:before", async (nitro) => {
838
+ await copyPublicAssets(nitro);
839
+ await nuxt.callHook("nitro:build:public-assets", nitro);
722
840
  });
723
841
  async function symlinkDist() {
724
842
  if (nitro.options.static) {
725
- const distDir$1 = resolve(nuxt.options.rootDir, "dist");
726
- if (!existsSync(distDir$1)) await promises.symlink(nitro.options.output.publicDir, distDir$1, "junction").catch(() => {});
843
+ const distDir = resolve(nuxt.options.rootDir, "dist");
844
+ if (!existsSync(distDir)) await promises.symlink(nitro.options.output.publicDir, distDir, "junction").catch(() => {});
727
845
  }
728
846
  }
729
- nuxt.hook("build:done", async () => {
730
- await nuxt.callHook("nitro:build:before", nitro);
731
- await prepare(nitro);
732
- if (nuxt.options.dev) return build(nitro);
733
- await prerender(nitro);
734
- logger.restoreAll();
735
- await build(nitro);
736
- logger.wrapAll();
737
- await symlinkDist();
738
- });
847
+ let waitUntilCompile;
739
848
  if (nuxt.options.dev) {
740
849
  for (const builder of ["webpack", "rspack"]) {
741
850
  nuxt.hook(`${builder}:compile`, ({ name, compiler }) => {
@@ -761,9 +870,27 @@ async function bundle(nuxt) {
761
870
  }));
762
871
  });
763
872
  nuxt.server = createDevServer(nitro);
764
- const waitUntilCompile = new Promise((resolve$1) => nitro.hooks.hook("compiled", () => resolve$1()));
765
- nuxt.hook("build:done", () => waitUntilCompile);
873
+ waitUntilCompile = new Promise((resolve) => nitro.hooks.hook("compiled", () => resolve()));
766
874
  }
875
+ nuxt.hook("build:done", async () => {
876
+ nuxt._perf?.startPhase("nitro:build");
877
+ try {
878
+ await nuxt.callHook("nitro:build:before", nitro);
879
+ await prepare(nitro);
880
+ if (nuxt.options.dev) {
881
+ await build(nitro);
882
+ await waitUntilCompile;
883
+ return;
884
+ }
885
+ await prerender(nitro);
886
+ logger.restoreAll();
887
+ await build(nitro);
888
+ logger.wrapAll();
889
+ await symlinkDist();
890
+ } finally {
891
+ nuxt._perf?.endPhase("nitro:build");
892
+ }
893
+ });
767
894
  }
768
895
  const RELATIVE_RE = /^([^.])/;
769
896
  function relativeWithDot(from, to) {
@@ -775,14 +902,13 @@ async function spaLoadingTemplatePath(nuxt) {
775
902
  }
776
903
  async function spaLoadingTemplate(nuxt) {
777
904
  if (nuxt.options.spaLoadingTemplate === false) return "";
778
- const spaLoadingTemplate$1 = await spaLoadingTemplatePath(nuxt);
905
+ const spaLoadingTemplate = await spaLoadingTemplatePath(nuxt);
779
906
  try {
780
- if (existsSync(spaLoadingTemplate$1)) return readFileSync(spaLoadingTemplate$1, "utf-8").trim();
907
+ if (existsSync(spaLoadingTemplate)) return readFileSync(spaLoadingTemplate, "utf-8").trim();
781
908
  } catch {}
782
909
  if (nuxt.options.spaLoadingTemplate === true) return template();
783
910
  if (nuxt.options.spaLoadingTemplate) logger.warn(`Could not load custom \`spaLoadingTemplate\` path as it does not exist: \`${nuxt.options.spaLoadingTemplate}\`.`);
784
911
  return "";
785
912
  }
786
-
787
913
  //#endregion
788
- export { bundle };
914
+ export { bundle };
@@ -0,0 +1,4 @@
1
+ /**
2
+ * h3 compatibility layer for Nuxt runtime code.
3
+ */
4
+ export * from "h3";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * h3 compatibility layer for Nuxt runtime code.
3
+ */
4
+ export * from "h3";
@@ -25,8 +25,8 @@ export default (async function errorhandler(error, event, { defaultHandler }) {
25
25
  // remove proto/hostname/port from URL
26
26
  const url = new URL(errorObject.url);
27
27
  errorObject.url = withoutBase(url.pathname, useRuntimeConfig(event).app.baseURL) + url.search + url.hash;
28
- // add default server message
29
- errorObject.message ||= "Server Error";
28
+ // add default server message (keep sanitized for unhandled errors)
29
+ errorObject.message = error.unhandled ? errorObject.message || "Server Error" : error.message || errorObject.message || "Server Error";
30
30
  // we will be rendering this error internally so we can pass along the error.data safely
31
31
  errorObject.data ||= error.data;
32
32
  errorObject.statusText ||= error.statusText || error.statusMessage;
@@ -1,2 +1,3 @@
1
- declare const _default;
2
- export default _default;
1
+ import type { EventHandler } from "h3";
2
+ declare const handler: EventHandler;
3
+ export default handler;
@@ -1,6 +1,6 @@
1
1
  import { useNitroApp } from "nitropack/runtime";
2
2
  import { destr } from "destr";
3
- import { defineEventHandler, getQuery, readBody, setResponseHeaders } from "h3";
3
+ import { createError, defineEventHandler, getQuery, readBody, setResponseHeaders } from "h3";
4
4
  import { resolveUnrefHeadInput } from "@unhead/vue";
5
5
  import { getRequestDependencies } from "vue-bundle-renderer/runtime";
6
6
  import { getQuery as getURLQuery } from "ufo";
@@ -10,7 +10,7 @@ import { getSSRRenderer } from "../utils/renderer/build-files.mjs";
10
10
  import { renderInlineStyles } from "../utils/renderer/inline-styles.mjs";
11
11
  import { getClientIslandResponse, getServerComponentHTML, getSlotIslandResponse } from "../utils/renderer/islands.mjs";
12
12
  const ISLAND_SUFFIX_RE = /\.json(?:\?.*)?$/;
13
- export default defineEventHandler(async (event) => {
13
+ const handler = defineEventHandler(async (event) => {
14
14
  const nitroApp = useNitroApp();
15
15
  setResponseHeaders(event, {
16
16
  "content-type": "application/json;charset=utf-8",
@@ -95,26 +95,38 @@ export default defineEventHandler(async (event) => {
95
95
  }
96
96
  return islandResponse;
97
97
  });
98
+ export default handler;
99
+ const ISLAND_PATH_PREFIX = "/__nuxt_island/";
100
+ const VALID_COMPONENT_NAME_RE = /^[a-z][\w.-]*$/i;
98
101
  async function getIslandContext(event) {
99
- // TODO: Strict validation for url
100
102
  let url = event.path || "";
101
103
  if (import.meta.prerender && event.path && await islandPropCache.hasItem(event.path)) {
102
104
  // rehydrate props from cache so we can rerender island if cache does not have it any more
103
105
  url = await islandPropCache.getItem(event.path);
104
106
  }
105
- const componentParts = url.substring("/__nuxt_island".length + 1).replace(ISLAND_SUFFIX_RE, "").split("_");
107
+ if (!url.startsWith(ISLAND_PATH_PREFIX)) {
108
+ throw createError({
109
+ statusCode: 400,
110
+ statusMessage: "Invalid island request path"
111
+ });
112
+ }
113
+ const componentParts = url.substring(ISLAND_PATH_PREFIX.length).replace(ISLAND_SUFFIX_RE, "").split("_");
106
114
  const hashId = componentParts.length > 1 ? componentParts.pop() : undefined;
107
115
  const componentName = componentParts.join("_");
108
- // TODO: Validate context
116
+ if (!componentName || !VALID_COMPONENT_NAME_RE.test(componentName)) {
117
+ throw createError({
118
+ statusCode: 400,
119
+ statusMessage: "Invalid island component name"
120
+ });
121
+ }
109
122
  const context = event.method === "GET" ? getQuery(event) : await readBody(event);
110
- const ctx = {
111
- url: "/",
112
- ...context,
123
+ // Only extract known context fields to prevent arbitrary data injection
124
+ return {
125
+ url: typeof context?.url === "string" ? context.url : "/",
113
126
  id: hashId,
114
127
  name: componentName,
115
128
  props: destr(context.props) || {},
116
129
  slots: {},
117
130
  components: {}
118
131
  };
119
- return ctx;
120
132
  }
@@ -1,2 +1,3 @@
1
- declare const _default;
2
- export default _default;
1
+ import type { EventHandler } from "h3";
2
+ declare const handler: EventHandler;
3
+ export default handler;
@@ -14,9 +14,9 @@ import { replaceIslandTeleports } from "../utils/renderer/islands.mjs";
14
14
  // @ts-expect-error virtual file
15
15
  import { renderSSRHeadOptions } from "#internal/unhead.config.mjs";
16
16
  // @ts-expect-error virtual file
17
- import { NUXT_ASYNC_CONTEXT, NUXT_EARLY_HINTS, NUXT_INLINE_STYLES, NUXT_JSON_PAYLOADS, NUXT_NO_SCRIPTS, NUXT_PAYLOAD_EXTRACTION, NUXT_RUNTIME_PAYLOAD_EXTRACTION, PARSE_ERROR_DATA } from "#internal/nuxt/nitro-config.mjs";
17
+ import { NUXT_ASYNC_CONTEXT, NUXT_EARLY_HINTS, NUXT_INLINE_STYLES, NUXT_JSON_PAYLOADS, NUXT_NO_SCRIPTS, NUXT_PAYLOAD_EXTRACTION, NUXT_PAYLOAD_INLINE, NUXT_RUNTIME_PAYLOAD_EXTRACTION, PARSE_ERROR_DATA } from "#internal/nuxt/nitro-config.mjs";
18
18
  // @ts-expect-error virtual file
19
- import { appHead, appTeleportAttrs, appTeleportTag, componentIslands, appManifest as isAppManifestEnabled } from "#internal/nuxt.config.mjs";
19
+ import { appHead, appTeleportAttrs, appTeleportTag, componentIslands } from "#internal/nuxt.config.mjs";
20
20
  // @ts-expect-error virtual file
21
21
  import entryIds from "#internal/nuxt/entry-ids.mjs";
22
22
  // @ts-expect-error virtual file
@@ -38,7 +38,7 @@ const APP_TELEPORT_CLOSE_TAG = HAS_APP_TELEPORTS ? `</${appTeleportTag}>` : "";
38
38
  const PAYLOAD_URL_RE = NUXT_JSON_PAYLOADS ? /^[^?]*\/_payload.json(?:\?.*)?$/ : /^[^?]*\/_payload.js(?:\?.*)?$/;
39
39
  const PAYLOAD_FILENAME = NUXT_JSON_PAYLOADS ? "_payload.json" : "_payload.js";
40
40
  let entryPath;
41
- export default defineRenderHandler(async (event) => {
41
+ const handler = defineRenderHandler(async (event) => {
42
42
  const nitroApp = useNitroApp();
43
43
  // Whether we're rendering an error page
44
44
  const ssrError = event.path.startsWith("/__nuxt_error") ? getQuery(event) : null;
@@ -72,12 +72,16 @@ export default defineRenderHandler(async (event) => {
72
72
  const routeOptions = getRouteRules(event);
73
73
  // Whether we are prerendering route or using ISR/SWR caching
74
74
  const _PAYLOAD_EXTRACTION = !ssrContext.noSSR && (import.meta.prerender && NUXT_PAYLOAD_EXTRACTION || NUXT_RUNTIME_PAYLOAD_EXTRACTION && (routeOptions.isr || routeOptions.cache));
75
+ // When NUXT_PAYLOAD_INLINE is true (payloadExtraction: 'client'), we inline the full payload
76
+ // in the HTML to avoid a separate _payload.json fetch on initial load (which would trigger a
77
+ // second render or lambda invocation). The _payload.json endpoint still works for client-side nav.
78
+ const _PAYLOAD_INLINE = !_PAYLOAD_EXTRACTION || NUXT_PAYLOAD_INLINE;
75
79
  const isRenderingPayload = (_PAYLOAD_EXTRACTION || import.meta.dev && routeOptions.prerender) && PAYLOAD_URL_RE.test(ssrContext.url);
76
80
  if (isRenderingPayload) {
77
81
  const url = ssrContext.url.substring(0, ssrContext.url.lastIndexOf("/")) || "/";
78
82
  ssrContext.url = url;
79
83
  event._path = event.node.req.url = url;
80
- if (import.meta.prerender && await payloadCache.hasItem(url)) {
84
+ if (payloadCache && await payloadCache.hasItem(url)) {
81
85
  return payloadCache.getItem(url);
82
86
  }
83
87
  }
@@ -128,16 +132,21 @@ export default defineRenderHandler(async (event) => {
128
132
  // Directly render payload routes
129
133
  if (isRenderingPayload) {
130
134
  const response = renderPayloadResponse(ssrContext);
131
- if (import.meta.prerender) {
135
+ if (payloadCache) {
132
136
  await payloadCache.setItem(ssrContext.url, response);
133
137
  }
134
138
  return response;
135
139
  }
136
- if (_PAYLOAD_EXTRACTION && import.meta.prerender) {
137
- // Hint nitro to prerender payload for this route
138
- appendResponseHeader(event, "x-nitro-prerender", joinURL(ssrContext.url.replace(/\?.*$/, ""), PAYLOAD_FILENAME));
139
- // Use same ssr context to generate payload for this route
140
- await payloadCache.setItem(ssrContext.url === "/" ? "/" : ssrContext.url.replace(/\/$/, ""), renderPayloadResponse(ssrContext));
140
+ if (_PAYLOAD_EXTRACTION) {
141
+ if (import.meta.prerender) {
142
+ // Hint nitro to prerender payload for this route
143
+ appendResponseHeader(event, "x-nitro-prerender", joinURL(ssrContext.url.replace(/\?.*$/, ""), PAYLOAD_FILENAME));
144
+ }
145
+ // Cache payload from the current SSR context so _payload.json requests can be served
146
+ // without a full re-render (during prerender via LRU+FS, at runtime via in-memory TTL cache)
147
+ if (payloadCache) {
148
+ await payloadCache.setItem(ssrContext.url === "/" ? "/" : ssrContext.url.replace(/\/$/, ""), renderPayloadResponse(ssrContext));
149
+ }
141
150
  }
142
151
  const NO_SCRIPTS = NUXT_NO_SCRIPTS || routeOptions.noScripts;
143
152
  // Setup head
@@ -167,7 +176,8 @@ export default defineRenderHandler(async (event) => {
167
176
  }] }, headEntryOptions);
168
177
  }
169
178
  // 1. Preload payloads and app manifest
170
- if (_PAYLOAD_EXTRACTION && !NO_SCRIPTS) {
179
+ // Skip preload when inlining full payload in HTML (no separate fetch needed for initial load)
180
+ if (_PAYLOAD_EXTRACTION && !_PAYLOAD_INLINE && !NO_SCRIPTS) {
171
181
  ssrContext.head.push({ link: [NUXT_JSON_PAYLOADS ? {
172
182
  rel: "preload",
173
183
  as: "fetch",
@@ -179,18 +189,6 @@ export default defineRenderHandler(async (event) => {
179
189
  href: payloadURL
180
190
  }] }, headEntryOptions);
181
191
  }
182
- if (isAppManifestEnabled && ssrContext["~preloadManifest"] && !NO_SCRIPTS) {
183
- ssrContext.head.push({ link: [{
184
- rel: "preload",
185
- as: "fetch",
186
- fetchpriority: "low",
187
- crossorigin: "anonymous",
188
- href: buildAssetsURL(`builds/meta/${ssrContext.runtimeConfig.app.buildId}.json`)
189
- }] }, {
190
- ...headEntryOptions,
191
- tagPriority: "low"
192
- });
193
- }
194
192
  // 2. Styles
195
193
  if (inlinedStyles.length) {
196
194
  ssrContext.head.push({ style: inlinedStyles });
@@ -225,7 +223,14 @@ export default defineRenderHandler(async (event) => {
225
223
  ssrContext.head.push({ link: getPreloadLinks(ssrContext, renderer.rendererContext) }, headEntryOptions);
226
224
  ssrContext.head.push({ link: getPrefetchLinks(ssrContext, renderer.rendererContext) }, headEntryOptions);
227
225
  // 5. Payloads
228
- ssrContext.head.push({ script: _PAYLOAD_EXTRACTION ? NUXT_JSON_PAYLOADS ? renderPayloadJsonScript({
226
+ ssrContext.head.push({ script: _PAYLOAD_INLINE ? NUXT_JSON_PAYLOADS ? renderPayloadJsonScript({
227
+ ssrContext,
228
+ data: ssrContext.payload
229
+ }) : renderPayloadScript({
230
+ ssrContext,
231
+ data: ssrContext.payload,
232
+ routeOptions
233
+ }) : NUXT_JSON_PAYLOADS ? renderPayloadJsonScript({
229
234
  ssrContext,
230
235
  data: splitPayload(ssrContext).initial,
231
236
  src: payloadURL
@@ -234,13 +239,6 @@ export default defineRenderHandler(async (event) => {
234
239
  data: splitPayload(ssrContext).initial,
235
240
  routeOptions,
236
241
  src: payloadURL
237
- }) : NUXT_JSON_PAYLOADS ? renderPayloadJsonScript({
238
- ssrContext,
239
- data: ssrContext.payload
240
- }) : renderPayloadScript({
241
- ssrContext,
242
- data: ssrContext.payload,
243
- routeOptions
244
242
  }) }, {
245
243
  ...headEntryOptions,
246
244
  tagPosition: "bodyClose",
@@ -249,7 +247,7 @@ export default defineRenderHandler(async (event) => {
249
247
  }
250
248
  // 6. Scripts
251
249
  if (!routeOptions.noScripts) {
252
- const tagPosition = _PAYLOAD_EXTRACTION && !NUXT_JSON_PAYLOADS ? "bodyClose" : "head";
250
+ const tagPosition = _PAYLOAD_EXTRACTION && !_PAYLOAD_INLINE && !NUXT_JSON_PAYLOADS ? "bodyClose" : "head";
253
251
  ssrContext.head.push({ script: Object.values(scripts).map((resource) => ({
254
252
  type: resource.module ? "module" : null,
255
253
  src: renderer.rendererContext.buildAssetsURL(resource.file),
@@ -281,6 +279,7 @@ export default defineRenderHandler(async (event) => {
281
279
  }
282
280
  };
283
281
  });
282
+ export default handler;
284
283
  function normalizeChunks(chunks) {
285
284
  const result = [];
286
285
  for (const _chunk of chunks) {
@@ -1,2 +1,3 @@
1
- declare const _default;
2
- export default _default;
1
+ import type { EventHandler } from "h3";
2
+ declare const handler: EventHandler;
3
+ export default handler;
@@ -1,7 +1,8 @@
1
1
  import { defineEventHandler, getRequestHeader } from "h3";
2
- export default defineEventHandler((event) => {
2
+ const handler = defineEventHandler((event) => {
3
3
  if (getRequestHeader(event, "x-nuxt-no-ssr")) {
4
4
  event.context.nuxt ||= {};
5
5
  event.context.nuxt.noSSR = true;
6
6
  }
7
7
  });
8
+ export default handler;
@@ -1,2 +1,3 @@
1
- declare const _default;
1
+ import type { NitroApp } from "nitropack/types";
2
+ declare const _default: (nitroApp: NitroApp) => void;
2
3
  export default _default;
@@ -1,5 +1,11 @@
1
- export declare const payloadCache: unknown;
2
- export declare const islandCache: unknown;
3
- export declare const islandPropCache: unknown;
4
- export declare const sharedPrerenderPromises: unknown;
5
- export declare const sharedPrerenderCache: unknown;
1
+ import type { Storage } from "unstorage";
2
+ export declare const payloadCache: Storage | null;
3
+ export declare const islandCache: Storage | null;
4
+ export declare const islandPropCache: Storage | null;
5
+ export declare const sharedPrerenderPromises: Map<string, Promise<any>> | null;
6
+ interface SharedPrerenderCache {
7
+ get<T = unknown>(key: string): Promise<T> | undefined;
8
+ set<T>(key: string, value: Promise<T>): Promise<void>;
9
+ }
10
+ export declare const sharedPrerenderCache: SharedPrerenderCache | null;
11
+ export {};
@@ -1,7 +1,7 @@
1
1
  import { useStorage } from "nitropack/runtime";
2
2
  // @ts-expect-error virtual file
3
- import { NUXT_SHARED_DATA } from "#internal/nuxt/nitro-config.mjs";
4
- export const payloadCache = import.meta.prerender ? useStorage("internal:nuxt:prerender:payload") : null;
3
+ import { NUXT_RUNTIME_PAYLOAD_EXTRACTION, NUXT_SHARED_DATA } from "#internal/nuxt/nitro-config.mjs";
4
+ export const payloadCache = import.meta.prerender ? useStorage("internal:nuxt:prerender:payload") : NUXT_RUNTIME_PAYLOAD_EXTRACTION ? useStorage("cache:nuxt:payload") : null;
5
5
  export const islandCache = import.meta.prerender ? useStorage("internal:nuxt:prerender:island") : null;
6
6
  export const islandPropCache = import.meta.prerender ? useStorage("internal:nuxt:prerender:island-props") : null;
7
7
  export const sharedPrerenderPromises = import.meta.prerender && NUXT_SHARED_DATA ? new Map() : null;
@@ -1 +1 @@
1
- export declare const defineAppConfig: unknown;
1
+ export declare const defineAppConfig: (config: any) => any;
@@ -357,7 +357,7 @@ function webComponentScript(base64HTML, startMinimized) {
357
357
  iframe.id = 'frame';
358
358
  iframe.src = 'data:text/html;base64,${base64HTML}';
359
359
  iframe.title = 'Detailed error stack trace';
360
- iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
360
+ iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-top-navigation-by-user-activation');
361
361
 
362
362
  const preview = el('div');
363
363
  preview.id = 'preview';
@@ -3,4 +3,4 @@ import type { H3Event } from "h3";
3
3
  * Nitro internal functions extracted from https://github.com/nitrojs/nitro/blob/v2/src/runtime/internal/utils.ts
4
4
  */
5
5
  export declare function isJsonRequest(event: H3Event): boolean;
6
- export declare function hasReqHeader(event: H3Event, name: string, includes: string);
6
+ export declare function hasReqHeader(event: H3Event, name: string, includes: string): boolean;
@@ -11,5 +11,5 @@ export function isJsonRequest(event) {
11
11
  }
12
12
  export function hasReqHeader(event, name, includes) {
13
13
  const value = getRequestHeader(event, name);
14
- return value && typeof value === "string" && value.toLowerCase().includes(includes);
14
+ return !!(value && typeof value === "string" && value.toLowerCase().includes(includes));
15
15
  }
@@ -1,4 +1,3 @@
1
- import { decodePath } from "ufo";
2
1
  import { useRuntimeConfig } from "nitropack/runtime";
3
2
  import { createHead } from "@unhead/vue/server";
4
3
  import { sharedPrerenderCache } from "../cache.mjs";
@@ -13,7 +12,7 @@ const PRERENDER_NO_SSR_ROUTES = new Set([
13
12
  ]);
14
13
  export function createSSRContext(event) {
15
14
  const ssrContext = {
16
- url: decodePath(event.path),
15
+ url: event.path,
17
16
  event,
18
17
  runtimeConfig: useRuntimeConfig(event),
19
18
  noSSR: !!NUXT_NO_SSR || event.context.nuxt?.noSSR || (import.meta.prerender ? PRERENDER_NO_SSR_ROUTES.has(event.path) : false),
@@ -10,9 +10,7 @@ interface Renderer {
10
10
  renderScripts: () => string;
11
11
  }>;
12
12
  }
13
- // -- SSR Renderer --
14
- export declare const getSSRRenderer: unknown;
13
+ export declare const getSSRRenderer: () => Promise<Renderer>;
15
14
  export declare function getRenderer(ssrContext: NuxtSSRContext): Promise<Renderer>;
16
- // @ts-expect-error file will be produced after app build
17
- export declare const getSSRStyles: unknown;
15
+ export declare const getSSRStyles: () => Promise<Record<string, () => Promise<string[]>>>;
18
16
  export {};
@@ -4,10 +4,10 @@ import { stringify, uneval } from "devalue";
4
4
  // @ts-expect-error virtual file
5
5
  import { appId, multiApp } from "#internal/nuxt.config.mjs";
6
6
  // @ts-expect-error virtual file
7
- import { NUXT_JSON_PAYLOADS, NUXT_NO_SSR, NUXT_PAYLOAD_EXTRACTION, NUXT_RUNTIME_PAYLOAD_EXTRACTION } from "#internal/nuxt/nitro-config.mjs";
7
+ import { NUXT_JSON_PAYLOADS, NUXT_NO_SSR } from "#internal/nuxt/nitro-config.mjs";
8
8
  export function renderPayloadResponse(ssrContext) {
9
9
  return {
10
- body: NUXT_JSON_PAYLOADS ? stringify(splitPayload(ssrContext).payload, ssrContext["~payloadReducers"]) : `export default ${devalue(splitPayload(ssrContext).payload)}`,
10
+ body: NUXT_JSON_PAYLOADS ? encodeForwardSlashes(stringify(splitPayload(ssrContext).payload, ssrContext["~payloadReducers"])) : `export default ${devalue(splitPayload(ssrContext).payload)}`,
11
11
  statusCode: getResponseStatus(ssrContext.event),
12
12
  statusMessage: getResponseStatusText(ssrContext.event),
13
13
  headers: {
@@ -17,7 +17,7 @@ export function renderPayloadResponse(ssrContext) {
17
17
  };
18
18
  }
19
19
  export function renderPayloadJsonScript(opts) {
20
- const contents = opts.data ? stringify(opts.data, opts.ssrContext["~payloadReducers"]) : "";
20
+ const contents = opts.data ? encodeForwardSlashes(stringify(opts.data, opts.ssrContext["~payloadReducers"])) : "";
21
21
  const payload = {
22
22
  "type": "application/json",
23
23
  "innerHTML": contents,
@@ -33,13 +33,29 @@ export function renderPayloadJsonScript(opts) {
33
33
  const config = uneval(opts.ssrContext.config);
34
34
  return [payload, { innerHTML: multiApp ? `window.__NUXT__=window.__NUXT__||{};window.__NUXT__[${JSON.stringify(appId)}]={config:${config}}` : `window.__NUXT__={};window.__NUXT__.config=${config}` }];
35
35
  }
36
+ /**
37
+ * Encode forward slashes as unicode escape sequences to prevent
38
+ * Google from treating them as internal links and trying to crawl them.
39
+ * @see https://github.com/nuxt/nuxt/issues/24175
40
+ */
41
+ function encodeForwardSlashes(str) {
42
+ return str.replaceAll("/", "\\u002F");
43
+ }
44
+ /**
45
+ * Escape a string for safe interpolation inside a double-quoted JavaScript string literal.
46
+ * Prevents XSS when user-controlled URLs are embedded in inline `<script>` tags.
47
+ */
48
+ function escapeJsString(str) {
49
+ return str.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\n", "\\n").replaceAll("\r", "\\r").replaceAll("/", "\\u002F").replaceAll("<", "\\u003C");
50
+ }
36
51
  export function renderPayloadScript(opts) {
37
52
  opts.data.config = opts.ssrContext.config;
38
- const _PAYLOAD_EXTRACTION = !opts.ssrContext.noSSR && (import.meta.prerender && NUXT_PAYLOAD_EXTRACTION || NUXT_RUNTIME_PAYLOAD_EXTRACTION && (opts.routeOptions.isr || opts.routeOptions.cache));
39
53
  const nuxtData = devalue(opts.data);
40
- if (_PAYLOAD_EXTRACTION) {
41
- const singleAppPayload = `import p from "${opts.src}";window.__NUXT__={...p,...(${nuxtData})}`;
42
- const multiAppPayload = `import p from "${opts.src}";window.__NUXT__=window.__NUXT__||{};window.__NUXT__[${JSON.stringify(appId)}]={...p,...(${nuxtData})}`;
54
+ if (opts.src) {
55
+ // Escape the URL to prevent XSS when interpolated into a JS string literal
56
+ const escapedSrc = escapeJsString(opts.src);
57
+ const singleAppPayload = `import p from "${escapedSrc}";window.__NUXT__={...p,...(${nuxtData})}`;
58
+ const multiAppPayload = `import p from "${escapedSrc}";window.__NUXT__=window.__NUXT__||{};window.__NUXT__[${JSON.stringify(appId)}]={...p,...(${nuxtData})}`;
43
59
  return [{
44
60
  type: "module",
45
61
  innerHTML: multiApp ? multiAppPayload : singleAppPayload
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxt/nitro-server",
3
- "version": "4.3.0",
3
+ "version": "4.4.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/nuxt/nuxt.git",
@@ -11,49 +11,71 @@
11
11
  "license": "MIT",
12
12
  "type": "module",
13
13
  "types": "./dist/index.d.mts",
14
+ "typesVersions": {
15
+ "*": {
16
+ "h3": [
17
+ "./dist/runtime/h3-compat.d.mts"
18
+ ]
19
+ }
20
+ },
14
21
  "exports": {
15
- ".": "./dist/index.mjs"
22
+ ".": "./dist/index.mjs",
23
+ "./h3": "./dist/runtime/h3-compat.mjs"
16
24
  },
17
25
  "files": [
18
26
  "dist"
19
27
  ],
20
28
  "dependencies": {
29
+ "@babel/plugin-syntax-typescript": "^7.28.6",
21
30
  "@nuxt/devalue": "^2.0.2",
22
- "@unhead/vue": "^2.1.2",
23
- "@vue/shared": "^3.5.27",
31
+ "@unhead/vue": "^2.1.12",
32
+ "@vue/shared": "^3.5.30",
24
33
  "consola": "^3.4.2",
25
34
  "defu": "^6.1.4",
26
35
  "destr": "^2.0.5",
27
- "devalue": "^5.6.2",
36
+ "devalue": "^5.6.3",
28
37
  "errx": "^0.1.0",
29
38
  "escape-string-regexp": "^5.0.0",
30
39
  "exsolve": "^1.0.8",
31
- "h3": "^1.15.5",
32
- "impound": "^1.0.0",
40
+ "h3": "^1.15.6",
41
+ "impound": "^1.1.5",
33
42
  "klona": "^2.0.6",
34
43
  "mocked-exports": "^0.1.1",
35
44
  "nitropack": "^2.13.1",
45
+ "nypm": "^0.6.5",
36
46
  "ohash": "^2.0.11",
37
47
  "pathe": "^2.0.3",
38
48
  "pkg-types": "^2.3.0",
39
- "rou3": "^0.7.12",
40
- "std-env": "^3.10.0",
49
+ "rou3": "^0.8.1",
50
+ "std-env": "^4.0.0",
41
51
  "ufo": "^1.6.3",
42
52
  "unctx": "^2.5.0",
43
53
  "unstorage": "^1.17.4",
44
- "vue": "^3.5.27",
54
+ "vue": "^3.5.30",
45
55
  "vue-bundle-renderer": "^2.2.0",
46
56
  "vue-devtools-stub": "^0.1.0",
47
- "@nuxt/kit": "4.3.0"
57
+ "@nuxt/kit": "4.4.2"
48
58
  },
49
59
  "peerDependencies": {
50
- "nuxt": "^4.3.0"
60
+ "@babel/plugin-proposal-decorators": "^7.25.0",
61
+ "@rollup/plugin-babel": "^6.0.0 || ^7.0.0",
62
+ "nuxt": "^4.4.2"
63
+ },
64
+ "peerDependenciesMeta": {
65
+ "@babel/plugin-proposal-decorators": {
66
+ "optional": true
67
+ },
68
+ "@rollup/plugin-babel": {
69
+ "optional": true
70
+ }
51
71
  },
52
72
  "devDependencies": {
53
- "obuild": "0.4.14",
54
- "vitest": "3.2.4",
55
- "@nuxt/schema": "4.3.0",
56
- "nuxt": "4.3.0"
73
+ "@babel/plugin-proposal-decorators": "7.29.0",
74
+ "@rollup/plugin-babel": "7.0.0",
75
+ "obuild": "0.4.32",
76
+ "vitest": "4.0.18",
77
+ "nuxt": "4.4.2",
78
+ "@nuxt/schema": "4.4.2"
57
79
  },
58
80
  "engines": {
59
81
  "node": "^20.19.0 || >=22.12.0"