diff --git a/packages/nuxt/src/core/nitro.ts b/packages/nuxt/src/core/nitro.ts index 57d5394cdf3..7273aadacd3 100644 --- a/packages/nuxt/src/core/nitro.ts +++ b/packages/nuxt/src/core/nitro.ts @@ -227,3 +227,9 @@ async function resolveHandlers (nuxt: Nuxt) { devHandlers } } + +declare module 'nitropack' { + interface NitroRouteOption { + ssr?: boolean + } +} diff --git a/packages/nuxt/src/core/runtime/nitro/renderer.ts b/packages/nuxt/src/core/runtime/nitro/renderer.ts index 828aec1a84f..8567b162f4b 100644 --- a/packages/nuxt/src/core/runtime/nitro/renderer.ts +++ b/packages/nuxt/src/core/runtime/nitro/renderer.ts @@ -3,6 +3,7 @@ import type { RenderResponse } from 'nitropack' import type { Manifest } from 'vite' import { appendHeader, getQuery } from 'h3' import devalue from '@nuxt/devalue' +import { createRouter as createMatcher } from 'radix3' import { joinURL } from 'ufo' import { renderToString as _renderToString } from 'vue/server-renderer' import { useRuntimeConfig, useNitroApp, defineRenderHandler } from '#internal/nitro' @@ -106,6 +107,9 @@ const getSPARenderer = lazyCachedFunction(async () => { } }) +// Set up route rule matcher +const routerOptions = createMatcher({ routes: useRuntimeConfig().nitro.routes }) + const PAYLOAD_CACHE = (process.env.NUXT_PAYLOAD_EXTRACTION && process.env.prerender) ? new Map() : null // TODO: Use LRU cache const PAYLOAD_URL_RE = /\/_payload(\.[a-zA-Z0-9]+)?.js(\?.*)?$/ @@ -128,6 +132,9 @@ export default defineRenderHandler(async (event) => { } } + // TODO: share across endpoints on event context + const routeOptions = event.context.routeOptions || routerOptions.lookup(url) || {} + // Initialize ssr context const ssrContext: NuxtSSRContext = { url, @@ -138,6 +145,7 @@ export default defineRenderHandler(async (event) => { noSSR: !!(process.env.NUXT_NO_SSR) || !!(event.req.headers['x-nuxt-no-ssr']) || + routeOptions.ssr === false || (process.env.prerender ? PRERENDER_NO_SSR_ROUTES.has(url) : false), error: !!ssrError, nuxt: undefined!, /* NuxtApp */ diff --git a/test/basic.test.ts b/test/basic.test.ts index cc964d76fce..65acabfea15 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -29,6 +29,12 @@ describe('server api', () => { }) }) +describe('route rules', () => { + it('should enable spa mode', async () => { + expect(await $fetch('/route-rules/spa')).toContain('serverRendered:false') + }) +}) + describe('pages', () => { it('render index', async () => { const html = await $fetch('/') diff --git a/test/fixtures/basic/nuxt.config.ts b/test/fixtures/basic/nuxt.config.ts index 2412474c4cf..01c53364ea6 100644 --- a/test/fixtures/basic/nuxt.config.ts +++ b/test/fixtures/basic/nuxt.config.ts @@ -19,6 +19,9 @@ export default defineNuxtConfig({ './extends/node_modules/foo' ], nitro: { + routes: { + '/route-rules/spa': { ssr: false } + }, output: { dir: process.env.NITRO_OUTPUT_DIR }, prerender: { routes: [ diff --git a/test/fixtures/basic/pages/route-rules/spa.vue b/test/fixtures/basic/pages/route-rules/spa.vue new file mode 100644 index 00000000000..6777da5c537 --- /dev/null +++ b/test/fixtures/basic/pages/route-rules/spa.vue @@ -0,0 +1,5 @@ +