diff --git a/packages/nuxt/src/core/nitro.ts b/packages/nuxt/src/core/nitro.ts index 55dcf1a197..56e72b6d1a 100644 --- a/packages/nuxt/src/core/nitro.ts +++ b/packages/nuxt/src/core/nitro.ts @@ -6,7 +6,7 @@ import { randomUUID } from 'uncrypto' import { joinURL, withTrailingSlash } from 'ufo' import { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, scanHandlers, writeTypes } from 'nitropack' import type { Nitro, NitroConfig } from 'nitropack' -import { logger, resolveIgnorePatterns, resolveNuxtModule } from '@nuxt/kit' +import { findPath, logger, resolveIgnorePatterns, resolveNuxtModule } from '@nuxt/kit' import escapeRE from 'escape-string-regexp' import { defu } from 'defu' import fsExtra from 'fs-extra' @@ -98,7 +98,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) { baseURL: nuxt.options.app.baseURL, virtual: { '#internal/nuxt.config.mjs': () => nuxt.vfs['#build/nuxt.config'], - '#spa-template': () => `export const template = ${JSON.stringify(spaLoadingTemplate(nuxt))}` + '#spa-template': async () => `export const template = ${JSON.stringify(await spaLoadingTemplate(nuxt))}` }, routeRules: { '/__nuxt_error': { cache: false } @@ -377,8 +377,9 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) { const nitro = await createNitro(nitroConfig) // Trigger Nitro reload when SPA loading template changes + const spaLoadingTemplateFilePath = await spaLoadingTemplatePath(nuxt) nuxt.hook('builder:watch', async (_event, path) => { - if (normalize(path) === spaLoadingTemplatePath(nuxt)) { + if (normalize(path) === spaLoadingTemplateFilePath) { await nitro.hooks.callHook('rollup:reload') } }) @@ -524,16 +525,20 @@ function relativeWithDot (from: string, to: string) { return relative(from, to).replace(/^([^.])/, './$1') || '.' } -function spaLoadingTemplatePath (nuxt: Nuxt) { - return typeof nuxt.options.spaLoadingTemplate === 'string' - ? resolve(nuxt.options.srcDir, nuxt.options.spaLoadingTemplate) - : resolve(nuxt.options.srcDir, 'app/spa-loading-template.html') +async function spaLoadingTemplatePath (nuxt: Nuxt) { + if (typeof nuxt.options.spaLoadingTemplate === 'string') { + return resolve(nuxt.options.srcDir, nuxt.options.spaLoadingTemplate) + } + + const possiblePaths = nuxt.options._layers.map(layer => join(layer.config.srcDir, 'app/spa-loading-template.html')) + + return await findPath(possiblePaths) ?? resolve(nuxt.options.srcDir, 'app/spa-loading-template.html') } -function spaLoadingTemplate (nuxt: Nuxt) { +async function spaLoadingTemplate (nuxt: Nuxt) { if (nuxt.options.spaLoadingTemplate === false) { return '' } - const spaLoadingTemplate = spaLoadingTemplatePath(nuxt) + const spaLoadingTemplate = await spaLoadingTemplatePath(nuxt) try { if (existsSync(spaLoadingTemplate)) { diff --git a/packages/schema/src/config/app.ts b/packages/schema/src/config/app.ts index f7c42ea260..87c9046025 100644 --- a/packages/schema/src/config/app.ts +++ b/packages/schema/src/config/app.ts @@ -181,9 +181,10 @@ export default defineUntypedSchema({ /** * Boolean or a path to an HTML file with the contents of which will be inserted into any HTML page * rendered with `ssr: false`. - * - If it is unset, it will use `~/app/spa-loading-template.html` if it exists. + * - If it is unset, it will use `~/app/spa-loading-template.html` file in one of your layers, if it exists. * - If it is false, no SPA loading indicator will be loaded. - * - If true, Nuxt will look for `~/app/spa-loading-template.html` file or a default Nuxt image will be used. + * - If true, Nuxt will look for `~/app/spa-loading-template.html` file in one of your layers, or a + * default Nuxt image will be used. * * Some good sources for spinners are [SpinKit](https://github.com/tobiasahlin/SpinKit) or [SVG Spinners](https://icones.js.org/collection/svg-spinners). * @example ~/app/spa-loading-template.html diff --git a/test/basic.test.ts b/test/basic.test.ts index c3fa14dfac..1b2172c7e3 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -64,6 +64,11 @@ describe('route rules', () => { await expectNoClientErrors('/route-rules/spa') }) + it('should not render loading template in spa mode if it is not enabled', async () => { + const html = await $fetch('/route-rules/spa') + expect(html).toContain('
') + }) + it('should allow defining route rules inline', async () => { const res = await fetch('/route-rules/inline') expect(res.status).toEqual(200)