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 () => {