VYPR
\n\n\n```\n\nwith `middleware/auth.ts` blocking unauthenticated access:\n\n```bash\n# Direct page request: blocked by middleware\ncurl -i http://localhost:3000/secret\n# -> 403 / redirect, depending on the middleware\n\n# Island request: middleware did not run before this fix\ncurl -i 'http://localhost:3000/__nuxt_island/page_secret_anyhash'\n# -> 200 OK, body includes

SECRET DATA

\n```\n\n### Patches\n\nPatched in `nuxt@4.4.6` and `nuxt@3.21.6` by [#35092](https://github.com/nuxt/nuxt/pull/35092). The Vue Router plugin now runs middleware and redirect handling for `page_*` islands (i.e. islands that originate from `.server.vue` files in `pages/`). The island handler propagates middleware-issued responses (`~renderResponse`), and a new `beforeResolve` guard returns HTTP 400 when the requested `page_` does not match the route component the URL resolves to.\n\nNon-page island components are unaffected - they continue to render without route middleware, by design.\n\n### Workarounds\n\nIf you cannot upgrade immediately:\n\n- Enforce authentication inside the `.server.vue` page itself, not via route middleware. Read the session from `useRequestEvent()` and `throw createError({ statusCode: 401 })` (or redirect) before returning data. This is the recommended pattern for islands regardless of this advisory.\n- Disable `experimental.componentIslands` if your app does not use the feature.\n- If your app must keep route-middleware-only auth, gate the `/__nuxt_island/page_*` URL prefix at your reverse proxy or in a server middleware.","additionalType":"https://schema.org/SoftwareApplication","sameAs":["https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2026-47200"]},"keywords":"CVE-2026-47200, medium, CWE-284, CWE-288, Nuxt Nuxt, Nuxt Nuxt","mentions":[{"@type":"SoftwareApplication","name":"Nuxt","applicationCategory":"SecurityApplication","publisher":{"@type":"Organization","name":"Nuxt"}},{"@type":"SoftwareApplication","name":"Nuxt","applicationCategory":"SecurityApplication","publisher":{"@type":"Organization","name":"Nuxt"}}],"isAccessibleForFree":true},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://portal.vyprsec.ai/"},{"@type":"ListItem","position":2,"name":"CVEs","item":"https://portal.vyprsec.ai/cves"},{"@type":"ListItem","position":3,"name":"CVE-2026-47200","item":"https://portal.vyprsec.ai/cves/CVE-2026-47200"}]}]}
Medium severity6.3GHSA Advisory· Published May 29, 2026· Updated May 29, 2026

Nuxt's route middleware is not enforced when rendering `.server.vue` pages via `/__nuxt_island/page_*`

CVE-2026-47200

Description

Summary

When experimental.componentIslands is enabled (default in Nuxt 4), any .server.vue file under pages/ is automatically registered as a server island under the key page_ and exposed via the /__nuxt_island/:name endpoint. Until this fix, requests through that endpoint rendered the page component directly via the SSR renderer without instantiating Vue Router, which meant route middleware declared on the page (including definePageMeta({ middleware })) did not run.

For Nuxt applications that gate a .server.vue *page* behind route middleware as their sole auth check, an unauthenticated attacker could bypass that check by requesting /__nuxt_island/page__ directly and receiving the server-rendered HTML.

Affected configurations

All three conditions must hold for an application to be vulnerable:

  1. experimental.componentIslands is enabled (the default in Nuxt 4; opt-in in Nuxt 3).
  2. The application defines one or more .server.vue files under pages/, registering them as routed pages.
  3. Authentication / authorization for at least one such page is enforced solely via route middleware (middleware/*.ts referenced from definePageMeta), without a server-side check inside the page or its data layer.

Applications that enforce auth inside the island's own data layer (server-only API routes, useRequestEvent + manual session checks, etc.) were not affected. The general "route middleware does not run for non-page island *components*" behaviour is documented and unchanged; this advisory concerns the .server.vue *page* case specifically, where running middleware is the user's clear expectation.

Details

  • Build (packages/nuxt/src/components/templates.ts): .server.vue pages are registered as island components with page_ prefix, making them addressable through /__nuxt_island/page__.
  • Runtime (packages/nitro-server/src/runtime/handlers/island.ts): the handler resolves the requested island component and renders it via renderer.renderToString(ssrContext). The Vue Router plugin previously short-circuited middleware execution whenever ssrContext.islandContext was set.
  • The two paths interact so that route middleware declared on the source page never runs.

Proof of concept

Given a page app/pages/secret.server.vue:




SECRET DATA

with middleware/auth.ts blocking unauthenticated access:

# Direct page request: blocked by middleware
curl -i http://localhost:3000/secret
# -> 403 / redirect, depending on the middleware

# Island request: middleware did not run before this fix
curl -i 'http://localhost:3000/__nuxt_island/page_secret_anyhash'
# -> 200 OK, body includes SECRET DATA

Patches

Patched in nuxt@4.4.6 and nuxt@3.21.6 by #35092. The Vue Router plugin now runs middleware and redirect handling for page_* islands (i.e. islands that originate from .server.vue files in pages/). The island handler propagates middleware-issued responses (~renderResponse), and a new beforeResolve guard returns HTTP 400 when the requested page_ does not match the route component the URL resolves to.

Non-page island components are unaffected - they continue to render without route middleware, by design.

Workarounds

If you cannot upgrade immediately:

  • Enforce authentication inside the .server.vue page itself, not via route middleware. Read the session from useRequestEvent() and throw createError({ statusCode: 401 }) (or redirect) before returning data. This is the recommended pattern for islands regardless of this advisory.
  • Disable experimental.componentIslands if your app does not use the feature.
  • If your app must keep route-middleware-only auth, gate the /__nuxt_island/page_* URL prefix at your reverse proxy or in a server middleware.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Server island endpoint bypasses route middleware on `.server.vue` pages, allowing unauthenticated access to auth-gated content in Nuxt 3/4.

Vulnerability

Nuxt versions prior to the fix (in Nuxt 3.x with experimental.componentIslands enabled, and default in Nuxt 4) automatically register any .server.vue file under pages/ as a server island with a key page_ and expose it via the /__nuxt_island/:name endpoint. When a request is made to this endpoint, the SSR renderer renders the page component directly without instantiating Vue Router, so route middleware declared via definePageMeta({ middleware }) does not execute. This affects applications where authentication or authorization for a .server.vue page relies solely on such middleware [1], [2].

Exploitation

An unauthenticated attacker only needs network access to the Nuxt application endpoint. By crafting a request to /__nuxt_island/page__ (e.g., /__nuxt_island/page_admin_abc123), they can directly retrieve the server-rendered HTML of the restricted page without triggering any route middleware. No authentication, user interaction, or special privileges are required [1], [2].

Impact

Successful exploitation leads to unauthorized disclosure of the server-rendered HTML of pages protected only by route middleware. This can expose sensitive information, such as admin dashboards, private data, or internal application state, that the attacker would otherwise be unable to access. The compromise is limited to information disclosure; however, depending on the page content, it may enable further attacks [1], [2].

Mitigation

The vulnerability is fixed in the latest Nuxt versions following pull request #35092 [4]. Users should update to the patched version that ensures route middleware runs when rendering .server.vue pages via the island endpoint. As a workaround, applications can enforce authentication inside the island's own data layer (e.g., server-only API routes, manual session checks in useRequestEvent) rather than relying solely on route middleware. No evidence of active exploitation or KEV listing is mentioned in the references [1], [2], [4].

AI Insight generated on May 29, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Nuxt/NuxtGHSA2 versions
    >= 4.0.0-alpha.1, <= 4.4.5+ 1 more
    • (no CPE)range: >= 4.0.0-alpha.1, <= 4.4.5
    • (no CPE)range: <4.4.6 (Nuxt 4) and <3.21.6 (Nuxt 3)

Patches

1
e6c9ab5f4835

fix(nuxt): run middleware for page islands (#35092)

https://github.com/nuxt/nuxtDaniel RoeMay 17, 2026via body-scan-shorthand
8 files changed · +140 20
  • packages/nitro-server/src/runtime/handlers/island.ts+31 2 modified
    @@ -39,19 +39,35 @@ const handler: ReturnType<typeof defineEventHandler> = defineEventHandler(async
       const renderer = await getSSRRenderer()
     
       const renderResult = await renderer.renderToString(ssrContext).catch(async (err) => {
    +    if (ssrContext['~renderResponse'] && (err as Error)?.message === 'skipping render') {
    +      return {} as Awaited<ReturnType<typeof renderer.renderToString>>
    +    }
         await ssrContext.nuxt?.hooks.callHook('app:error', err)
         throw err
       })
     
    +  // Fire `app:rendered` before checking `~renderResponse` (matches `renderer.ts`), so
    +  // anything hooking into it, like `useCookie`, will still work on redirect/reject.
    +  await ssrContext.nuxt?.hooks.callHook('app:rendered', { ssrContext, renderResult })
    +
    +  if (ssrContext['~renderResponse']) {
    +    const response = ssrContext['~renderResponse']
    +    if (response.status && response.status >= 400) {
    +      throw new HTTPError({
    +        status: response.status,
    +        statusText: response.statusText,
    +      })
    +    }
    +    return returnIslandResponse(event, response)
    +  }
    +
       // Handle errors
       if (ssrContext.payload?.error) {
         throw ssrContext.payload.error
       }
     
       const inlinedStyles = await renderInlineStyles(ssrContext.modules ?? [])
     
    -  await ssrContext.nuxt?.hooks.callHook('app:rendered', { ssrContext, renderResult })
    -
       if (inlinedStyles.length) {
         ssrContext.head.push({ style: inlinedStyles })
       }
    @@ -109,6 +125,19 @@ const handler: ReturnType<typeof defineEventHandler> = defineEventHandler(async
     
     export default handler
     
    +function returnIslandResponse (event: H3Event, response: Partial<RenderResponse>) {
    +  for (const header in response.headers || {}) {
    +    event.res.headers.set(header, response.headers![header]!)
    +  }
    +  if (response.status) {
    +    event.res.status = response.status
    +  }
    +  if (response.statusText) {
    +    event.res.statusText = response.statusText
    +  }
    +  return response.body
    +}
    +
     const ISLAND_PATH_PREFIX = '/__nuxt_island/'
     const VALID_COMPONENT_NAME_RE = /^[a-z][\w.-]*$/i
     
    
  • packages/nuxt/src/components/runtime/server-component.ts+8 2 modified
    @@ -37,8 +37,8 @@ export const createServerComponent = (name: string) => {
     }
     
     /* @__NO_SIDE_EFFECTS__ */
    -export const createIslandPage = (name: string) => {
    -  return defineComponent({
    +export const createIslandPage = (name: string, islandKey?: string) => {
    +  const component = defineComponent({
         name,
         inheritAttrs: false,
         props: { lazy: Boolean },
    @@ -72,4 +72,10 @@ export const createIslandPage = (name: string) => {
           }
         },
       })
    +
    +  // we use this to validate that a server page is rendering the correct url
    +  if (import.meta.server && islandKey) {
    +    (component as any).__nuxt_island = islandKey
    +  }
    +  return component
     }
    
  • packages/nuxt/src/components/templates.ts+12 3 modified
    @@ -1,5 +1,6 @@
     import { isAbsolute, join, relative, resolve } from 'pathe'
     import { genDynamicImport, genDynamicTypeImport, genObjectKey } from 'knitwork'
    +import { hash } from 'ohash'
     import { distDir } from '../dirs.ts'
     import type { NuxtApp, NuxtPluginTemplate, NuxtTemplate } from 'nuxt/schema'
     
    @@ -79,7 +80,7 @@ export const componentsIslandsTemplate: NuxtTemplate = {
       filename: 'components.islands.mjs',
       getContents ({ app, nuxt }) {
         if (!nuxt.options.experimental.componentIslands) {
    -      return 'export const islandComponents = {}'
    +      return 'export const islandComponents = {}\nexport const pageIslandRoutes = {}'
         }
     
         const components = app.components
    @@ -90,9 +91,14 @@ export const componentsIslandsTemplate: NuxtTemplate = {
           (component.mode === 'server' && !components.some(c => c.pascalName === component.pascalName && c.mode === 'client')),
         )
     
    -    const pageExports = pages?.filter(p => (p.mode === 'server' && p.file && p.name)).map((p) => {
    +    const serverPages = pages?.filter(p => (p.mode === 'server' && p.file && p.name)) || []
    +    const pageExports = serverPages.map((p) => {
           return `"page_${p.name}": defineAsyncComponent(${genDynamicImport(p.file!)}.then(c => c.default || c))`
    -    }) || []
    +    })
    +    // map each `page_<name>` to a stable, opaque marker derived from the page's file path.
    +    const pageIslandRoutes = serverPages.map((p) => {
    +      return `  "page_${p.name}": ${JSON.stringify(hash(relative(nuxt.options.rootDir, p.file!)))}`
    +    })
     
         return [
           'import { defineAsyncComponent } from \'vue\'',
    @@ -105,6 +111,9 @@ export const componentsIslandsTemplate: NuxtTemplate = {
             },
           ).concat(pageExports).join(',\n'),
           '}',
    +      'export const pageIslandRoutes = import.meta.client ? {} : {',
    +      pageIslandRoutes.join(',\n'),
    +      '}',
         ].join('\n')
       },
     }
    
  • packages/nuxt/src/pages/runtime/plugins/router.ts+24 4 modified
    @@ -18,6 +18,8 @@ import _routes, { handleHotUpdate } from '#build/routes'
     import routerOptions, { hashMode } from '#build/router.options.mjs'
     // @ts-expect-error virtual file
     import { globalMiddleware, namedMiddleware } from '#build/middleware'
    +// @ts-expect-error virtual file
    +import { pageIslandRoutes } from '#build/components.islands.mjs'
     
     // https://github.com/vuejs/router/blob/4a0cc8b9c1e642cdf47cc007fa5bbebde70afc66/packages/router/src/history/html5.ts#L37
     function createCurrentLocation (
    @@ -140,7 +142,9 @@ const plugin: Plugin<{ router: Router }> = defineNuxtPlugin({
         }
     
         const error = useError()
    -    if (import.meta.client || !nuxtApp.ssrContext?.islandContext) {
    +    // we only skip redirect handlers for component islands, not page islands
    +    const isServerPage = import.meta.server && nuxtApp.ssrContext?.islandContext?.name?.startsWith('page_')
    +    if (import.meta.client || !nuxtApp.ssrContext?.islandContext || isServerPage) {
           router.afterEach(async (to, _from, failure) => {
             delete nuxtApp._processingMiddleware
     
    @@ -188,8 +192,8 @@ const plugin: Plugin<{ router: Router }> = defineNuxtPlugin({
     
         syncCurrentRoute()
     
    -    if (import.meta.server && nuxtApp.ssrContext?.islandContext) {
    -      // We're in an island context, and don't need to handle middleware or redirections
    +    if (import.meta.server && nuxtApp.ssrContext?.islandContext && !isServerPage) {
    +      // we don't need to handle middleware or redirections for non-page islands
           return { provide: { router } }
         }
     
    @@ -202,7 +206,7 @@ const plugin: Plugin<{ router: Router }> = defineNuxtPlugin({
           }
           nuxtApp._processingMiddleware = true
     
    -      if (import.meta.client || !nuxtApp.ssrContext?.islandContext) {
    +      if (import.meta.client || !nuxtApp.ssrContext?.islandContext || isServerPage) {
             type MiddlewareDef = string | RouteMiddleware
             const middlewareEntries = new Set<MiddlewareDef>([...globalMiddleware, ...nuxtApp._middleware.global])
             for (const component of to.matched) {
    @@ -272,6 +276,22 @@ const plugin: Plugin<{ router: Router }> = defineNuxtPlugin({
           }
         })
     
    +    if (isServerPage) {
    +      // validate that a server page is rendering the correct url
    +      router.beforeResolve((to) => {
    +        const expected = pageIslandRoutes[nuxtApp.ssrContext!.islandContext!.name]
    +        const actual = to.matched.find(m => (m.components?.default as any)?.__nuxt_island)
    +          ?.components?.default as any
    +        if (!expected || expected !== actual?.__nuxt_island) {
    +          nuxtApp.ssrContext!['~renderResponse'] = {
    +            status: 400,
    +            statusText: 'Invalid island request path',
    +          }
    +          return false
    +        }
    +      })
    +    }
    +
         router.onError(async () => {
           delete nuxtApp._processingMiddleware
           await nuxtApp.callHook('page:loading:end')
    
  • packages/nuxt/src/pages/utils.ts+15 9 modified
    @@ -1,7 +1,7 @@
     import { runInNewContext } from 'node:vm'
     import fs from 'node:fs'
     
    -import { normalize } from 'pathe'
    +import { normalize, relative } from 'pathe'
     import { joinURL } from 'ufo'
     import { getLayerDirectories, resolveFiles, resolvePath, useNuxt } from '@nuxt/kit'
     import { genArrayFromRaw, genDynamicImport, genImport, genSafeVariableName } from 'knitwork'
    @@ -342,23 +342,23 @@ interface NormalizeRoutesOptions {
       clientComponentRuntime: string
     }
     
    -function normalizeComponent (page: NuxtPage, pageImport: string, routeName: string | undefined): string {
    +function normalizeComponent (page: NuxtPage, pageImport: string, routeName: string | undefined, islandKey: string | undefined): string {
       if (page.mode === 'server') {
    -    return `() => createIslandPage(${routeName})`
    +    return `() => createIslandPage(${routeName}, import.meta.server ? ${islandKey} : undefined)`
       }
       if (page.mode === 'client') {
         return `() => createClientPage(${pageImport})`
       }
       return pageImport
     }
     
    -function normalizeComponentWithName (page: NuxtPage, isSyncImport: boolean | undefined, pageImportName: string, pageImport: string, routeName: string | undefined, metaRouteName: string): string {
    +function normalizeComponentWithName (page: NuxtPage, isSyncImport: boolean | undefined, pageImportName: string, pageImport: string, routeName: string | undefined, metaRouteName: string, islandKey: string | undefined): string {
       if (isSyncImport) {
         return `Object.assign(${pageImportName}, { __name: ${metaRouteName} })`
       }
       // Server components already receive the name via createIslandPage(name)
       if (page.mode === 'server') {
    -    return `() => createIslandPage(${routeName})`
    +    return `() => createIslandPage(${routeName}, import.meta.server ? ${islandKey} : undefined)`
       }
       // Client components return a processed component (not a module with .default)
       if (page.mode === 'client') {
    @@ -381,6 +381,7 @@ export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> =
               metaFiltered[key] = page.meta![key]
             }
           }
    +
           const skipAlias = toArray(page.alias).every(val => !val)
     
           const route: NormalizedRoute = {
    @@ -420,9 +421,14 @@ export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> =
           const pageImport = isSyncImport ? pageImportName : genDynamicImport(file)
           const metaRouteName = `${metaImportName}?.name ?? ${route.name}`
     
    +      // we use this to validate that a server page is rendering the correct url
    +      const islandKey = page.mode === 'server' && page.file
    +        ? JSON.stringify(hash(relative(nuxt.options.rootDir, page.file)))
    +        : undefined
    +
           const component = nuxt.options.experimental.normalizePageNames
    -        ? normalizeComponentWithName(page, isSyncImport, pageImportName, pageImport, route.name, metaRouteName)
    -        : normalizeComponent(page, pageImport, route.name)
    +        ? normalizeComponentWithName(page, isSyncImport, pageImportName, pageImport, route.name, metaRouteName, islandKey)
    +        : normalizeComponent(page, pageImport, route.name, islandKey)
     
           const metaRoute: NormalizedRoute = {
             name: metaRouteName,
    @@ -437,9 +443,9 @@ export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> =
           if (page.mode === 'server') {
             metaImports.add(`
     let _createIslandPage
    -async function createIslandPage (name) {
    +async function createIslandPage (name, islandKey) {
       _createIslandPage ||= await import(${JSON.stringify(options?.serverComponentRuntime)}).then(r => r.createIslandPage)
    -  return _createIslandPage(name)
    +  return _createIslandPage(name, islandKey)
     };`)
           } else if (page.mode === 'client') {
             metaImports.add(`
    
  • test/fixtures/server-components/app/middleware/island-auth.ts+4 0 added
    @@ -0,0 +1,4 @@
    +export default defineNuxtRouteMiddleware(() => {
    +  useCookie('island-auth-marker').value = 'set-from-island-middleware'
    +  return navigateTo('/login', { redirectCode: 302 })
    +})
    
  • test/fixtures/server-components/app/pages/gated-server-page.server.vue+11 0 added
    @@ -0,0 +1,11 @@
    +<template>
    +  <div id="gated-server-page">
    +    SUPER-SECRET-PAGE-ISLAND-BODY
    +  </div>
    +</template>
    +
    +<script setup lang="ts">
    +definePageMeta({
    +  middleware: 'island-auth',
    +})
    +</script>
    
  • test/server-components.test.ts+35 0 modified
    @@ -502,6 +502,41 @@ describe('hash binding', () => {
       })
     })
     
    +describe('page-island middleware', () => {
    +  it('runs page middleware and honours redirects for `page_*` islands', async () => {
    +    const res = await fetch(islandURL('page_gated-server-page', {
    +      context: { url: '/gated-server-page' },
    +    }), { redirect: 'manual' })
    +    // page middleware calls `navigateTo('/login', { redirectCode: 302 })`
    +    expect(res.status).toBe(302)
    +    expect(res.headers.get('location')).toContain('/login')
    +    const body = await res.text()
    +    expect(body).not.toContain('SUPER-SECRET-PAGE-ISLAND-BODY')
    +    // this asserts the island handler fires `app:rendered` even when middleware short-circuits response
    +    expect(res.headers.get('set-cookie')).toContain('island-auth-marker=set-from-island-middleware')
    +  })
    +
    +  it('still renders unguarded `page_*` islands', async () => {
    +    const res = await fetch(islandURL('page_server-page', {
    +      context: { url: '/server-page' },
    +    }))
    +    expect(res.status).toBe(200)
    +    const body = await res.json() as NuxtIslandResponse
    +    expect(body.html).toContain('Hello this is a server page')
    +  })
    +
    +  it('rejects a `page_*` island whose url routes to a different page', async () => {
    +    // Forging `page_gated-server-page` with `url=/server-page` would render the gated
    +    // page's HTML while running the (empty) middleware for the unguarded page.
    +    const res = await fetch(islandURL('page_gated-server-page', {
    +      context: { url: '/server-page' },
    +    }), { redirect: 'manual' })
    +    expect(res.status).toBe(400)
    +    const body = await res.text()
    +    expect(body).not.toContain('SUPER-SECRET-PAGE-ISLAND-BODY')
    +  })
    +})
    +
     describe.skipIf(isDev || isWebpack)('regressions', () => {
       // https://github.com/nuxt/nuxt/issues/26527
       it.fails('renders <Counter nuxt-client /> when nested two levels deep in server components', async () => {
    

Vulnerability mechanics

Root cause

"The island SSR handler renders `.server.vue` pages without instantiating Vue Router, so route middleware declared via `definePageMeta({ middleware })` never executes."

Attack vector

An attacker sends a GET request to `/__nuxt_island/page_<routeName>_<anyhash>` — the endpoint that serves server-island components [ref_id=1]. Because the island handler renders the component via `renderer.renderToString(ssrContext)` without instantiating Vue Router, any route middleware (e.g. an auth guard) declared on the source `.server.vue` page is skipped [ref_id=1]. The attacker receives the full server-rendered HTML of the protected page, bypassing the intended access control. The only preconditions are that `experimental.componentIslands` is enabled, a `.server.vue` page exists under `pages/`, and that page relies solely on route middleware for authentication [ref_id=1].

Affected code

Build-time registration in `packages/nuxt/src/components/templates.ts` assigns `.server.vue` pages island component keys with a `page_` prefix [ref_id=1]. Runtime handling in `packages/nitro-server/src/runtime/handlers/island.ts` resolves the island and renders it via `renderer.renderToString(ssrContext)` [ref_id=1]. The Vue Router plugin previously short-circuited middleware execution whenever `ssrContext.islandContext` was set, so route middleware never ran for these `page_*` islands [ref_id=1].

What the fix does

The patch modifies the Vue Router plugin so that route middleware and redirect handling now execute for `page_*` islands — those originating from `.server.vue` files in `pages/` [ref_id=1]. The island handler propagates middleware-issued responses (`~renderResponse`), and a new `beforeResolve` guard returns HTTP 400 when the requested `page_<name>` does not match the route component the URL resolves to [ref_id=1]. Non-page island components remain unaffected and continue to render without route middleware, which is by design [ref_id=1].

Preconditions

  • configexperimental.componentIslands must be enabled (default in Nuxt 4, opt-in in Nuxt 3)
  • configAt least one .server.vue file must exist under pages/
  • configAuthentication for that page must be enforced solely via route middleware (middleware/*.ts referenced from definePageMeta), without additional server-side checks inside the page or its data layer

Reproduction

Create a page `app/pages/secret.server.vue` with `<script setup lang="ts"> definePageMeta({ middleware: 'auth' }) </script>` and a middleware that blocks unauthenticated users. Request the direct page URL (`curl http://localhost:3000/secret`) — the middleware blocks the request. Then request the island endpoint (`curl 'http://localhost:3000/__nuxt_island/page_secret_anyhash'`) — before the fix this returns 200 OK with the protected content [ref_id=1].

Generated on May 29, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.