next.js
next.js copied to clipboard
Dynamic Import of Client Component from Server Component Not Code Split
Link to the code that reproduces this issue
https://github.com/ajwootto/nextjs-dynamic-loading-repro
To Reproduce
- yarn and start the repro server with
yarn dev
- observe that the "DynamicClientComponent" is only ever dynamically imported using the Next.js dynamic helper, and is never rendered.
- Visit the root page on localhost and check the network tab
- view the contents of
page.js
- Observe that the implementation code for
DynamicClientComponent
is present in that bundle. Search for the words "Dynamic Component That Should Not Be Here" to see it.
Another case using Suspense
- Navigate to
/suspend
- Observe that there's a suspense fallback rendered which is then replaced with the dynamically loaded client component
- Check the initial page.js file again for the same string. It is present in that bundle still.
Current vs. Expected behavior
Current: A client component that is dynamically imported by a server component is included in the initial client bundle, regardless of whether it is rendered or not during server rendering. Even if it is rendered inside a Suspense boundary it is still included in the initial bundle.
Expected: The client bundle should not contain any client component code which is dynamically imported but not rendered. It should also not include any client component code that is dynamically imported and rendered inside a Suspense boundary from a server component. The implementation of that component should be streamed later to the client.
The repro example is the simplest possible case where the imported component is never called no matter what, but I would also expect something like this to work:
const ConditionalComponent = dynamic(() => import('./DynamicClientComponent'))
export default function Home() {
// some arbitrary condition which could be determined dynamically (think feature flags etc.)
const shouldRender = false
// ConditionalComponent should not be sent since on this server render pass it was never rendered
return shouldRender && <ConditionalComponent/>
}
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 23.2.0: Wed Nov 15 21:53:18 PST 2023; root:xnu-10002.61.3~2/RELEASE_ARM64_T6000
Binaries:
Node: 20.10.0
npm: 10.2.3
Yarn: 3.6.2
pnpm: 8.6.12
Relevant Packages:
next: 14.1.1-canary.7 // Latest available version is detected (14.1.1-canary.7).
eslint-config-next: N/A
react: 18.2.0
react-dom: 18.2.0
typescript: 5.1.3
Next.js Config:
output: N/A
Which area(s) are affected? (Select all that apply)
App Router, Dynamic imports (next/dynamic)
Which stage(s) are affected? (Select all that apply)
next dev (local), next build (local), next start (local)
Additional context
Not sure if this is just a docs or understanding issue, or whether what I think should be the behaviour here is impossible, but it really seems intuitive to me that a dynamic import should not be included in the initial client bundle if its not called during render.