diff --git a/packages/nuxt/src/app/components/nuxt-layout.ts b/packages/nuxt/src/app/components/nuxt-layout.ts index 7472235a3e..887f5fa9d4 100644 --- a/packages/nuxt/src/app/components/nuxt-layout.ts +++ b/packages/nuxt/src/app/components/nuxt-layout.ts @@ -1,12 +1,12 @@ import type { DefineComponent, MaybeRef, VNode } from 'vue' -import { Suspense, Transition, computed, defineComponent, h, inject, mergeProps, nextTick, onMounted, provide, ref, unref } from 'vue' +import { Suspense, computed, defineComponent, h, inject, mergeProps, nextTick, onMounted, provide, ref, unref } from 'vue' import type { RouteLocationNormalizedLoaded } from 'vue-router' import type { PageMeta } from '../../pages/runtime/composables' import { useRoute, useRouter } from '../composables/router' import { useNuxtApp } from '../nuxt' -import { _wrapIf } from './utils' +import { _wrapInTransition } from './utils' import { LayoutMetaSymbol, PageRouteSymbol } from './injections' // @ts-expect-error virtual file @@ -80,7 +80,7 @@ export default defineComponent({ const transitionProps = route.meta.layoutTransition ?? defaultLayoutTransition // We avoid rendering layout transition if there is no layout to render - return _wrapIf(Transition, hasLayout && transitionProps, { + return _wrapInTransition(hasLayout && transitionProps, { default: () => h(Suspense, { suspensible: true, onResolve: () => { nextTick(done) } }, { default: () => h( LayoutProvider, diff --git a/packages/nuxt/src/app/components/utils.ts b/packages/nuxt/src/app/components/utils.ts index 7a38c6fdfc..30157a6162 100644 --- a/packages/nuxt/src/app/components/utils.ts +++ b/packages/nuxt/src/app/components/utils.ts @@ -1,5 +1,5 @@ -import { createStaticVNode, h } from 'vue' -import type { Component, RendererNode, VNode } from 'vue' +import { Transition, createStaticVNode, h } from 'vue' +import type { RendererNode, VNode } from 'vue' // eslint-disable-next-line import { isString, isPromise, isArray, isObject } from '@vue/shared' import type { RouteLocationNormalized } from 'vue-router' @@ -10,9 +10,8 @@ import { START_LOCATION } from '#build/pages' * Internal utility * @private */ -export const _wrapIf = (component: Component, props: any, slots: any) => { - props = props === true ? {} : props - return { default: () => props ? h(component, props, slots) : slots.default?.() } +export const _wrapInTransition = (props: any, children: any) => { + return { default: () => import.meta.client && props ? h(Transition, props === true ? {} : props, children) : children.default?.() } } const ROUTE_KEY_PARENTHESES_RE = /(:\w+)\([^)]+\)/g diff --git a/packages/nuxt/src/pages/runtime/page.ts b/packages/nuxt/src/pages/runtime/page.ts index 3f82ed8393..9deebfdf22 100644 --- a/packages/nuxt/src/pages/runtime/page.ts +++ b/packages/nuxt/src/pages/runtime/page.ts @@ -1,4 +1,4 @@ -import { Fragment, Suspense, Transition, defineComponent, h, inject, nextTick, ref, watch } from 'vue' +import { Fragment, Suspense, defineComponent, h, inject, nextTick, ref, watch } from 'vue' import type { KeepAliveProps, TransitionProps, VNode } from 'vue' import { RouterView } from 'vue-router' import { defu } from 'defu' @@ -9,7 +9,7 @@ import type { RouterViewSlotProps } from './utils' import { RouteProvider } from '#app/components/route-provider' import { useNuxtApp } from '#app/nuxt' import { useRouter } from '#app/composables/router' -import { _wrapIf } from '#app/components/utils' +import { _wrapInTransition } from '#app/components/utils' import { LayoutMetaSymbol, PageRouteSymbol } from '#app/components/injections' // @ts-expect-error virtual file import { appKeepalive as defaultKeepaliveConfig, appPageTransition as defaultPageTransition } from '#build/nuxt.config.mjs' @@ -101,8 +101,30 @@ export default defineComponent({ nuxtApp.callHook('page:loading:end') pageLoadingEndHookAlreadyCalled = true } + previousPageKey = key + if (import.meta.server) { + vnode = h(Suspense, { + suspensible: true, + }, { + default: () => { + const providerVNode = h(RouteProvider, { + key: key || undefined, + vnode: slots.default ? h(Fragment, undefined, slots.default(routeProps)) : routeProps.Component, + route: routeProps.route, + renderKey: key || undefined, + vnodeRef: pageRef, + }) + return providerVNode + }, + }) + + return vnode + } + + // Client side rendering + const hasTransition = !!(props.transition ?? routeProps.route.meta.pageTransition ?? defaultPageTransition) const transitionProps = hasTransition && _mergeTransitionProps([ props.transition, @@ -112,7 +134,7 @@ export default defineComponent({ ].filter(Boolean)) const keepaliveConfig = props.keepalive ?? routeProps.route.meta.keepalive ?? (defaultKeepaliveConfig as KeepAliveProps) - vnode = _wrapIf(Transition, hasTransition && transitionProps, + vnode = _wrapInTransition(hasTransition && transitionProps, wrapInKeepAlive(keepaliveConfig, h(Suspense, { suspensible: true, onPending: () => nuxtApp.callHook('page:start', routeProps.Component), @@ -134,7 +156,7 @@ export default defineComponent({ trackRootNodes: hasTransition, vnodeRef: pageRef, }) - if (import.meta.client && keepaliveConfig) { + if (keepaliveConfig) { (providerVNode.type as any).name = (routeProps.Component.type as any).name || (routeProps.Component.type as any).__name || 'RouteProvider' } return providerVNode diff --git a/test/bundle.test.ts b/test/bundle.test.ts index 167a0ab9f6..3cfbc1ba23 100644 --- a/test/bundle.test.ts +++ b/test/bundle.test.ts @@ -78,7 +78,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM const serverDir = join(rootDir, '.output-inline/server') const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir) - expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"566k"`) + expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"564k"`) const modules = await analyzeSizes(['node_modules/**/*'], serverDir) expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"92.3k"`)