diff --git a/packages/nuxt/src/app/components/layout.ts b/packages/nuxt/src/app/components/layout.ts index 8a0d633996..b4f4b793c8 100644 --- a/packages/nuxt/src/app/components/layout.ts +++ b/packages/nuxt/src/app/components/layout.ts @@ -33,8 +33,9 @@ export default defineComponent({ const layoutRef = ref() context.expose({ layoutRef }) + const done = nuxtApp.deferHydration() + return () => { - const done = nuxtApp.deferHydration() const hasLayout = layout.value && layout.value in layouts if (process.dev && layout.value && !hasLayout && layout.value !== 'default') { console.warn(`Invalid layout \`${layout.value}\` selected.`) diff --git a/packages/nuxt/src/pages/runtime/page.ts b/packages/nuxt/src/pages/runtime/page.ts index b7cebd25a9..081b3ff956 100644 --- a/packages/nuxt/src/pages/runtime/page.ts +++ b/packages/nuxt/src/pages/runtime/page.ts @@ -46,6 +46,8 @@ export default defineComponent({ const _layoutMeta = inject(LayoutMetaSymbol, null) let vnode: VNode + const done = nuxtApp.deferHydration() + return () => { return h(RouterView, { name: props.name, route: props.route, ...attrs }, { default: (routeProps: RouterViewSlotProps) => { @@ -76,7 +78,6 @@ export default defineComponent({ } const key = generateRouteKey(routeProps, props.pageKey) - const done = nuxtApp.deferHydration() const hasTransition = !!(props.transition ?? routeProps.route.meta.pageTransition ?? defaultPageTransition) const transitionProps = hasTransition && _mergeTransitionProps([ diff --git a/test/basic.test.ts b/test/basic.test.ts index c069af1a11..d1d68f765a 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -1034,6 +1034,16 @@ describe('deferred app suspense resolve', () => { it('should wait for all suspense instance on initial hydration', async () => { await behaviour('/internal-layout/async-parent/child') }) + it('should fully hydrate even if there is a redirection on a page with `ssr: false`', async () => { + const page = await createPage('/hydration/spa-redirection/start') + await page.waitForLoadState('networkidle') + + // Wait for all pending micro ticks to be cleared in case hydration hasn't finished yet. + await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10))) + + const html = await page.getByRole('document').innerHTML() + expect(html).toContain('fully hydrated and ready to go') + }) }) describe('nested suspense', () => { diff --git a/test/fixtures/basic/nuxt.config.ts b/test/fixtures/basic/nuxt.config.ts index 5a6d38d138..82ea65ef03 100644 --- a/test/fixtures/basic/nuxt.config.ts +++ b/test/fixtures/basic/nuxt.config.ts @@ -48,6 +48,7 @@ export default defineNuxtConfig({ }, routeRules: { '/route-rules/spa': { ssr: false }, + '/hydration/spa-redirection/**': { ssr: false }, '/no-scripts': { experimentalNoScripts: true } }, output: { dir: process.env.NITRO_OUTPUT_DIR }, diff --git a/test/fixtures/basic/pages/hydration/spa-redirection/end.vue b/test/fixtures/basic/pages/hydration/spa-redirection/end.vue new file mode 100644 index 0000000000..0f117f8bbb --- /dev/null +++ b/test/fixtures/basic/pages/hydration/spa-redirection/end.vue @@ -0,0 +1,12 @@ + + + + + {{ ready }} + + diff --git a/test/fixtures/basic/pages/hydration/spa-redirection/start.vue b/test/fixtures/basic/pages/hydration/spa-redirection/start.vue new file mode 100644 index 0000000000..854812be39 --- /dev/null +++ b/test/fixtures/basic/pages/hydration/spa-redirection/start.vue @@ -0,0 +1,11 @@ + + + + + Tests whether hydration is properly resolved when loading + +