diff --git a/packages/nuxt/src/app/composables/manifest.ts b/packages/nuxt/src/app/composables/manifest.ts index c828faeeb3..5a293e6f9a 100644 --- a/packages/nuxt/src/app/composables/manifest.ts +++ b/packages/nuxt/src/app/composables/manifest.ts @@ -1,7 +1,7 @@ import type { MatcherExport, RouteMatcher } from 'radix3' import { createMatcherFromExport, createRouter as createRadixRouter, toRouteMatcher } from 'radix3' import { defu } from 'defu' -import { useRuntimeConfig } from '../nuxt' +import { useNuxtApp, useRuntimeConfig } from '../nuxt' // @ts-expect-error virtual file import { appManifest as isAppManifestEnabled } from '#build/nuxt.config.mjs' // @ts-expect-error virtual file @@ -24,9 +24,14 @@ function fetchManifest () { if (!isAppManifestEnabled) { throw new Error('[nuxt] app manifest should be enabled with `experimental.appManifest`') } - manifest = $fetch(buildAssetsURL(`builds/meta/${useRuntimeConfig().app.buildId}.json`), { - responseType: 'json', - }) + if (import.meta.server) { + // @ts-expect-error virtual file + manifest = import('#app-manifest') + } else { + manifest = $fetch(buildAssetsURL(`builds/meta/${useRuntimeConfig().app.buildId}.json`), { + responseType: 'json', + }) + } manifest.then((m) => { matcher = createMatcherFromExport(m.matcher) }).catch((e) => { @@ -40,12 +45,16 @@ export function getAppManifest (): Promise { if (!isAppManifestEnabled) { throw new Error('[nuxt] app manifest should be enabled with `experimental.appManifest`') } + if (import.meta.server) { + useNuxtApp().ssrContext!._preloadManifest = true + } return manifest || fetchManifest() } /** @since 3.7.4 */ export async function getRouteRules (url: string) { if (import.meta.server) { + useNuxtApp().ssrContext!._preloadManifest = true const _routeRulesMatcher = toRouteMatcher( createRadixRouter({ routes: useRuntimeConfig().nitro!.routeRules }), ) diff --git a/packages/nuxt/src/app/composables/payload.ts b/packages/nuxt/src/app/composables/payload.ts index e24d34feab..f2107afd8e 100644 --- a/packages/nuxt/src/app/composables/payload.ts +++ b/packages/nuxt/src/app/composables/payload.ts @@ -85,15 +85,18 @@ async function _importPayload (payloadURL: string) { } /** @since 3.0.0 */ export async function isPrerendered (url = useRoute().path) { + const nuxtApp = useNuxtApp() // Note: Alternative for server is checking x-nitro-prerender header - if (!appManifest) { return !!useNuxtApp().payload.prerenderedAt } + if (!appManifest) { return !!nuxtApp.payload.prerenderedAt } url = withoutTrailingSlash(url) const manifest = await getAppManifest() if (manifest.prerendered.includes(url)) { return true } - const rules = await getRouteRules(url) - return !!rules.prerender && !rules.redirect + return nuxtApp.runWithContext(async () => { + const rules = await getRouteRules(url) + return !!rules.prerender && !rules.redirect + }) } let payloadCache: NuxtPayload | null = null diff --git a/packages/nuxt/src/app/nuxt.ts b/packages/nuxt/src/app/nuxt.ts index e0751a8f42..82c2e3d37b 100644 --- a/packages/nuxt/src/app/nuxt.ts +++ b/packages/nuxt/src/app/nuxt.ts @@ -83,6 +83,8 @@ export interface NuxtSSRContext extends SSRContext { get (key: string): Promise | undefined set (key: string, value: Promise): Promise } + /** @internal */ + _preloadManifest?: boolean } export interface NuxtPayload { diff --git a/packages/nuxt/src/core/nitro.ts b/packages/nuxt/src/core/nitro.ts index a3a156fc48..ae73575601 100644 --- a/packages/nuxt/src/core/nitro.ts +++ b/packages/nuxt/src/core/nitro.ts @@ -264,7 +264,18 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) { nuxt.options.alias['#app-manifest'] = join(tempDir, `meta/${buildId}.json`) + // write stub manifest before build so external import of #app-manifest can be resolved + if (!nuxt.options.dev) { + nuxt.hook('build:before', async () => { + await fsp.mkdir(join(tempDir, 'meta'), { recursive: true }) + await fsp.writeFile(join(tempDir, `meta/${buildId}.json`), JSON.stringify({})) + }) + } + nuxt.hook('nitro:config', (config) => { + config.alias ||= {} + config.alias['#app-manifest'] = join(tempDir, `meta/${buildId}.json`) + const rules = config.routeRules for (const rule in rules) { if (!(rules[rule] as any).appMiddleware) { continue } @@ -340,6 +351,11 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) { }) } + // add stub alias to allow vite to resolve import + if (!nuxt.options.experimental.appManifest) { + nuxt.options.alias['#app-manifest'] = 'unenv/runtime/mock/proxy' + } + // Add fallback server for `ssr: false` const FORWARD_SLASH_RE = /\//g if (!nuxt.options.ssr) { diff --git a/packages/nuxt/src/core/runtime/nitro/renderer.ts b/packages/nuxt/src/core/runtime/nitro/renderer.ts index d81861aae1..024a54fa96 100644 --- a/packages/nuxt/src/core/runtime/nitro/renderer.ts +++ b/packages/nuxt/src/core/runtime/nitro/renderer.ts @@ -31,7 +31,7 @@ import { renderSSRHeadOptions } from '#internal/unhead.config.mjs' import type { NuxtPayload, NuxtSSRContext } from '#app' // @ts-expect-error virtual file -import { appHead, appId, appRootAttrs, appRootTag, appTeleportAttrs, appTeleportTag, componentIslands, multiApp } from '#internal/nuxt.config.mjs' +import { appHead, appId, appRootAttrs, appRootTag, appTeleportAttrs, appTeleportTag, componentIslands, appManifest as isAppManifestEnabled, multiApp } from '#internal/nuxt.config.mjs' // @ts-expect-error virtual file import { buildAssetsURL, publicAssetsURL } from '#internal/nuxt/paths' @@ -380,7 +380,7 @@ export default defineRenderHandler(async (event): Promise