diff --git a/packages/next/src/shared/lib/lazy-dynamic/loadable.tsx b/packages/next/src/shared/lib/lazy-dynamic/loadable.tsx index 1aa448a61e366..31b6e459e93d8 100644 --- a/packages/next/src/shared/lib/lazy-dynamic/loadable.tsx +++ b/packages/next/src/shared/lib/lazy-dynamic/loadable.tsx @@ -1,7 +1,7 @@ import { Suspense, lazy } from 'react' import { BailoutToCSR } from './dynamic-bailout-to-csr' import type { ComponentModule } from './types' -import { PreloadCss } from './preload-css' +import { PreloadChunks } from './preload-chunks' // Normalize loader to return the module as form { default: Component } for `React.lazy`. // Also for backward compatible since next/dynamic allows to resolve a component directly with loader @@ -52,7 +52,7 @@ function Loadable(options: LoadableOptions) { <> {/* During SSR, we need to preload the CSS from the dynamic component to avoid flash of unstyled content */} {typeof window === 'undefined' ? ( - + ) : null} diff --git a/packages/next/src/shared/lib/lazy-dynamic/preload-chunks.tsx b/packages/next/src/shared/lib/lazy-dynamic/preload-chunks.tsx new file mode 100644 index 0000000000000..dc332f8545a22 --- /dev/null +++ b/packages/next/src/shared/lib/lazy-dynamic/preload-chunks.tsx @@ -0,0 +1,64 @@ +'use client' + +import { getExpectedRequestStore } from '../../../client/components/request-async-storage.external' +import { preload } from 'react-dom' + +export function PreloadChunks({ + moduleIds, +}: { + moduleIds: string[] | undefined +}) { + // Early return in client compilation and only load requestStore on server side + if (typeof window !== 'undefined') { + return null + } + + const requestStore = getExpectedRequestStore('next/dynamic preload') + const allFiles = [] + + // Search the current dynamic call unique key id in react loadable manifest, + // and find the corresponding CSS files to preload + if (requestStore.reactLoadableManifest && moduleIds) { + const manifest = requestStore.reactLoadableManifest + for (const key of moduleIds) { + if (!manifest[key]) continue + const chunks = manifest[key].files + allFiles.push(...chunks) + } + } + + if (allFiles.length === 0) { + return null + } + + return ( + <> + {allFiles.map((chunk) => { + const href = `${requestStore.assetPrefix}/_next/${encodeURI(chunk)}` + const isCss = chunk.endsWith('.css') + // If it's stylesheet we use `precedence` o help hoist with React Float. + // For stylesheets we actually need to render the CSS because nothing else is going to do it so it needs to be part of the component tree. + // The `preload` for stylesheet is not optional. + if (isCss) { + return ( + + ) + } else { + // If it's script we use ReactDOM.preload to preload the resources + preload(href, { + as: 'script', + fetchPriority: 'low', + }) + return null + } + })} + + ) +} diff --git a/packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx b/packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx deleted file mode 100644 index aac260fc2b251..0000000000000 --- a/packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx +++ /dev/null @@ -1,47 +0,0 @@ -'use client' - -import { getExpectedRequestStore } from '../../../client/components/request-async-storage.external' - -export function PreloadCss({ moduleIds }: { moduleIds: string[] | undefined }) { - // Early return in client compilation and only load requestStore on server side - if (typeof window !== 'undefined') { - return null - } - - const requestStore = getExpectedRequestStore('next/dynamic css') - const allFiles = [] - - // Search the current dynamic call unique key id in react loadable manifest, - // and find the corresponding CSS files to preload - if (requestStore.reactLoadableManifest && moduleIds) { - const manifest = requestStore.reactLoadableManifest - for (const key of moduleIds) { - if (!manifest[key]) continue - const cssFiles = manifest[key].files.filter((file: string) => - file.endsWith('.css') - ) - allFiles.push(...cssFiles) - } - } - - if (allFiles.length === 0) { - return null - } - - return ( - <> - {allFiles.map((file) => { - return ( - - ) - })} - - ) -} diff --git a/packages/next/src/shared/lib/lazy-dynamic/types.ts b/packages/next/src/shared/lib/lazy-dynamic/types.ts index ad5eb8407120d..b2a5b4f6ddfc9 100644 --- a/packages/next/src/shared/lib/lazy-dynamic/types.ts +++ b/packages/next/src/shared/lib/lazy-dynamic/types.ts @@ -7,7 +7,6 @@ export declare type LoaderComponent

= Promise< export declare type Loader

= () => LoaderComponent

export type LoadableGeneratedOptions = { - webpack?(): any modules?: string[] } diff --git a/test/e2e/app-dir/dynamic-css/index.test.ts b/test/e2e/app-dir/dynamic-css/index.test.ts index 94f54019fe296..f7bf90245a746 100644 --- a/test/e2e/app-dir/dynamic-css/index.test.ts +++ b/test/e2e/app-dir/dynamic-css/index.test.ts @@ -11,10 +11,14 @@ describe('app dir - dynamic css', () => { return } - it('should preload css of dynamic component during SSR', async () => { + it('should preload all chunks of dynamic component during SSR', async () => { const $ = await next.render$('/ssr') - const cssLinks = $('link[rel="stylesheet"]') + const cssLinks = $('link[rel="stylesheet"][data-precedence="dynamic"]') expect(cssLinks.attr('href')).toContain('.css') + + const preloadJsChunks = $('link[rel="preload"]') + expect(preloadJsChunks.attr('as')).toBe('script') + expect(preloadJsChunks.attr('fetchpriority')).toContain(`low`) }) it('should only apply corresponding css for page loaded that /ssr', async () => {