@nuxt/nitro-server 4.3.1 → 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/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,37 +16,78 @@ 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
- var version = "4.3.1";
24
+ //#region package.json
25
+ var version = "4.4.2";
26
+ //#endregion
27
+ //#region src/utils.ts
23
28
  function toArray(value) {
24
29
  return Array.isArray(value) ? value : [value];
25
30
  }
26
31
  let _distDir = dirname(fileURLToPath(import.meta.url));
27
32
  if (/(?:chunks|shared)$/.test(_distDir)) _distDir = dirname(_distDir);
28
33
  const distDir = _distDir;
34
+ //#endregion
35
+ //#region ../ui-templates/dist/templates/spa-loading-icon.ts
29
36
  const template = () => {
30
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>";
31
38
  };
39
+ //#endregion
40
+ //#region ../nuxt/src/core/plugins/import-protection.ts
32
41
  function createImportProtectionPatterns(nuxt, options) {
33
42
  const patterns = [];
34
43
  const context = contextFlags[options.context];
35
- 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`." : "")]);
36
- 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
+ ]);
37
58
  patterns.push([/(^|node_modules\/)@vue\/composition-api/]);
38
- for (const mod of nuxt.options._installedModules) if (mod.entryPath) patterns.push([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
+ ]);
39
64
  for (const i of [
40
65
  /(^|node_modules\/)@nuxt\/(cli|kit|test-utils)/,
41
66
  /(^|node_modules\/)nuxi/,
42
67
  /(^|node_modules\/)nitro(?:pack)?(?:-nightly)?(?:$|\/)(?!(?:dist\/)?(?:node_modules|presets|runtime|types))/,
43
68
  /(^|node_modules\/)nuxt\/(config|kit|schema)/
44
- ]) patterns.push([i, `This module cannot be imported in ${context}.`]);
45
- 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
+ ]);
46
79
  if (options.context === "nuxt-app" || options.context === "shared") {
47
- patterns.push([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}.`]);
48
- 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
+ ]);
49
91
  }
50
92
  return patterns;
51
93
  }
@@ -54,6 +96,8 @@ const contextFlags = {
54
96
  "nuxt-app": "the Vue part of your app",
55
97
  "shared": "the #shared directory"
56
98
  };
99
+ //#endregion
100
+ //#region src/templates.ts
57
101
  const nitroSchemaTemplate = {
58
102
  filename: "types/nitro-nuxt.d.ts",
59
103
  async getContents({ nuxt }) {
@@ -130,6 +174,8 @@ declare module 'nitropack/types' {
130
174
  function renderReference(ref, baseDir) {
131
175
  return `/// <reference ${"path" in ref ? `path="${isAbsolute(ref.path) ? relative(baseDir, ref.path) : ref.path}"` : `types="${ref.types}"`} />`;
132
176
  }
177
+ //#endregion
178
+ //#region src/index.ts
133
179
  const logLevelMapReverse = {
134
180
  silent: 0,
135
181
  info: 3,
@@ -269,7 +315,8 @@ async function bundle(nuxt) {
269
315
  `export const NUXT_JSON_PAYLOADS = ${!!nuxt.options.experimental.renderJsonPayloads}`,
270
316
  `export const NUXT_ASYNC_CONTEXT = ${!!nuxt.options.experimental.asyncContext}`,
271
317
  `export const NUXT_SHARED_DATA = ${!!nuxt.options.experimental.sharedPrerenderData}`,
272
- `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}`,
273
320
  `export const NUXT_RUNTIME_PAYLOAD_EXTRACTION = ${hasCachedRoutes}`
274
321
  ].join("\n");
275
322
  }
@@ -374,7 +421,8 @@ async function bundle(nuxt) {
374
421
  "appLayout",
375
422
  "cache",
376
423
  "isr",
377
- "swr"
424
+ "swr",
425
+ "ssr"
378
426
  ];
379
427
  function getRouteRulesRouter() {
380
428
  const routeRulesRouter = createRouter();
@@ -435,6 +483,7 @@ async function bundle(nuxt) {
435
483
  nuxt.hook("nitro:init", (nitro) => {
436
484
  nitro.hooks.hook("build:before", (nitro) => {
437
485
  for (const [route, value] of Object.entries(nitro.options.routeRules)) if (!route.endsWith("*") && !route.endsWith("/_payload.json")) {
486
+ if (value.ssr === false) continue;
438
487
  if (value.isr || value.cache || value.prerender && nuxt.options.dev) {
439
488
  const payloadKey = route + "/_payload.json";
440
489
  const defaults = {};
@@ -510,6 +559,55 @@ async function bundle(nuxt) {
510
559
  nitroConfig.virtual["#build/dist/server/styles.mjs"] = "export default {}";
511
560
  if (process.platform === "win32") nitroConfig.virtual["#build/dist/server/styles.mjs".replace(FORWARD_SLASH_RE, "\\")] = "export default {}";
512
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
+ }
513
611
  nitroConfig.rollupConfig.plugins = await nitroConfig.rollupConfig.plugins || [];
514
612
  nitroConfig.rollupConfig.plugins = toArray(nitroConfig.rollupConfig.plugins);
515
613
  const sharedDir = withTrailingSlash(resolve(nuxt.options.rootDir, nuxt.options.dir.shared));
@@ -521,10 +619,12 @@ async function bundle(nuxt) {
521
619
  ];
522
620
  nitroConfig.rollupConfig.plugins.push(ImpoundPlugin.rollup({
523
621
  cwd: nuxt.options.rootDir,
622
+ trace: true,
524
623
  include: sharedPatterns,
525
624
  patterns: createImportProtectionPatterns(nuxt, { context: "shared" })
526
625
  }), ImpoundPlugin.rollup({
527
626
  cwd: nuxt.options.rootDir,
627
+ trace: true,
528
628
  patterns: createImportProtectionPatterns(nuxt, { context: "nitro-app" }),
529
629
  exclude: [/node_modules[\\/]nitro(?:pack)?(?:-nightly)?[\\/]|(packages|@nuxt)[\\/]nitro-server(?:-nightly)?[\\/](src|dist)[\\/]runtime[\\/]/, ...sharedPatterns]
530
630
  }));
@@ -575,10 +675,12 @@ async function bundle(nuxt) {
575
675
  tsConfig.compilerOptions.paths[alias] = [absolutePath];
576
676
  if (isDirectory) tsConfig.compilerOptions.paths[`${alias}/*`] = [`${absolutePath}/*`];
577
677
  }
678
+ nuxt._perf?.startPhase("nitro:createNitro");
578
679
  const nitro = await createNitro(nitroConfig, {
579
680
  compatibilityDate: nuxt.options.compatibilityDate,
580
681
  dotenv: nuxt.options._loadOptions?.dotenv
581
682
  });
683
+ nuxt._perf?.endPhase("nitro:createNitro");
582
684
  if (nuxt.options.experimental.serverAppConfig === false && nitro.options.imports) {
583
685
  nitro.options.imports.presets ||= [];
584
686
  nitro.options.imports.presets = nitro.options.imports.presets.map((preset) => typeof preset === "string" || !("imports" in preset) ? preset : {
@@ -586,10 +688,7 @@ async function bundle(nuxt) {
586
688
  imports: preset.imports.filter((i) => i !== "useAppConfig")
587
689
  });
588
690
  }
589
- if (nitro.options.static && nuxt.options.experimental.payloadExtraction === void 0) {
590
- logger.warn("Using experimental payload extraction for full-static output. You can opt-out by setting `experimental.payloadExtraction` to `false`.");
591
- nuxt.options.experimental.payloadExtraction = true;
592
- }
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'`.");
593
692
  const spaLoadingTemplateFilePath = await spaLoadingTemplatePath(nuxt);
594
693
  nuxt.hook("builder:watch", async (_event, relativePath) => {
595
694
  if (resolve(nuxt.options.srcDir, relativePath) === spaLoadingTemplateFilePath) await nitro.hooks.callHook("rollup:reload");
@@ -606,8 +705,52 @@ async function bundle(nuxt) {
606
705
  } });
607
706
  nuxt._nitro = nitro;
608
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
+ });
609
736
  nuxt["~runtimeDependencies"] ||= [];
610
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
+ });
611
754
  nitro.vfs = nuxt.vfs = nitro.vfs || nuxt.vfs || {};
612
755
  nuxt.hook("close", () => nitro.hooks.callHook("close"));
613
756
  nitro.hooks.hook("prerender:routes", (routes) => {
@@ -701,16 +844,7 @@ async function bundle(nuxt) {
701
844
  if (!existsSync(distDir)) await promises.symlink(nitro.options.output.publicDir, distDir, "junction").catch(() => {});
702
845
  }
703
846
  }
704
- nuxt.hook("build:done", async () => {
705
- await nuxt.callHook("nitro:build:before", nitro);
706
- await prepare(nitro);
707
- if (nuxt.options.dev) return build(nitro);
708
- await prerender(nitro);
709
- logger.restoreAll();
710
- await build(nitro);
711
- logger.wrapAll();
712
- await symlinkDist();
713
- });
847
+ let waitUntilCompile;
714
848
  if (nuxt.options.dev) {
715
849
  for (const builder of ["webpack", "rspack"]) {
716
850
  nuxt.hook(`${builder}:compile`, ({ name, compiler }) => {
@@ -736,9 +870,27 @@ async function bundle(nuxt) {
736
870
  }));
737
871
  });
738
872
  nuxt.server = createDevServer(nitro);
739
- const waitUntilCompile = new Promise((resolve) => nitro.hooks.hook("compiled", () => resolve()));
740
- nuxt.hook("build:done", () => waitUntilCompile);
873
+ waitUntilCompile = new Promise((resolve) => nitro.hooks.hook("compiled", () => resolve()));
741
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
+ });
742
894
  }
743
895
  const RELATIVE_RE = /^([^.])/;
744
896
  function relativeWithDot(from, to) {
@@ -758,4 +910,5 @@ async function spaLoadingTemplate(nuxt) {
758
910
  if (nuxt.options.spaLoadingTemplate) logger.warn(`Could not load custom \`spaLoadingTemplate\` path as it does not exist: \`${nuxt.options.spaLoadingTemplate}\`.`);
759
911
  return "";
760
912
  }
913
+ //#endregion
761
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";
@@ -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
  }