next.js: React lazy or next dynamic don't reduce an app route's "First Load JS"
Verify canary release
- I verified that the issue exists in the latest Next.js canary release
Provide environment information
Operating System:
Platform: linux
Arch: x64
Version: #22 SMP Tue Jan 10 18:39:00 UTC 2023
Binaries:
Node: 18.16.0
npm: 9.5.1
Yarn: 1.22.19
pnpm: N/A
Relevant packages:
next: 13.4.2-canary.0
eslint-config-next: 13.4.0
react: 18.2.0
react-dom: 18.2.0
typescript: 5.0.4
Which area(s) of Next.js are affected? (leave empty if unsure)
No response
Link to the code that reproduces this issue
To Reproduce
https://github.com/antoineol/repro-next-lazy-build-size
yarn build
, you will get:
Open app/page.tsx
, comment this line:
const DynamicHeavy = lazy(() => import("./DynamicHeavy"));
Then build again. You will get:
Same result if using the dynamic
wrapper instead of lazy
.
Describe the Bug
My understanding is that the “First Load JS” is the minimal bundle size to render the page, excluding lazy-loaded content that are in separate chunks, and counted separately. I expect “First Load JS” to be what matters for a quick initial rendering to improve the lighthouse score.
In this experimentation, lazy does not seem to reduce this initial bundle size. It behaves like a traditional “import” that includes everything in the initial bundle.
Unless I’m missing something?
Expected Behavior
The “First Load JS” remains the same, with or without this line commented:
const DynamicHeavy = lazy(() => import("./DynamicHeavy"));
Since I would expect it to he a separate chunk, not counted in the initial render.
Which browser are you using? (if relevant)
No response
How are you deploying your application? (if relevant)
No response
About this issue
- Original URL
- State: closed
- Created a year ago
- Reactions: 10
- Comments: 20 (8 by maintainers)
it’s kind of a joke that at least since May code splitting doesn’t work in NextJS app directory. We are now on version 14.0.1, app router is recommended for all new applications since May, (v13.4.0 right?) and core features don’t work at all (code splitting, cache & revalidation (fixed in 14.0.2-canary.3 tho, finally)). 2.4k open issues and Vercel is focusing on server actions that are just a sugar layer on top of the api routes. Like… seriously?
Let’s imagine any app that has a CMS - currently EVERY SINGLE PAGE, no matter if renders 1 component or 100, gets the same, huge bundle with all components included.
Like seriously, using Next stopped to be fun with version 13, as most of the development time is spend on debugging the newly introduced bugs or discovering that core features that worked since v1.0.0 now just don’t and no ones gives a s**t about it for half of the year. I get that you want to introduce new features, Server Actions looks nice, but for God’s sake - first make sure the core features are working properly and issues relating core features are not left open for half of the year.
I don’t know what are your processes of development, but would be nice to revisit them, as almost every new update introduces more bugs than it fixes.
App router is not stable and shouldn’t be used by any project larger than a landing page.
PS: code split works fine with pages directory (v14.0.1).
Experiencing the same issue in app router. It’s not possible to use app router for large applications, because pagespeed performance is really bad, when using many components.
Hi, sorry for the late response, definitely missed the discussion here as there’re too many issues atm. Wanna share a bit more that how can you do code-splitting better.
Solution
If we look at the reproduction case that shared in the issue description, it’s using
next/dynamic
call in the server components, loading a client component. The quick way to make code splitting work is to usenext/dynamic
in client components.So you could either add a client component in between the module you’re loading or mark the page.js as client component if that works for you.
Why
Calling
next/dynamic
with client component in server component is so far not able to do code splitting. It’s like you’re trying to code splitting in server components where you might not worry about the chunks size especially when you want those async loaded chunks to be loaded on client. So you need to move them to a client component and callnext/dynamic
there to split the module you’d like to separate into other chunks.Using
next/dynamic
in server components that loading a client component is still including everything in server side rendering, and that client component doesn’t know it needs to be split into another chunk in browser chunks.We’ll improve the education resource to make it more clear and any feedback to it is welcomed, thanks
Same issue here. CMS, all content is dynamic, and the page is slow (not just page speed numbers, but visible too for the eyes). We need a solution for dynamic import, suspense, lazy load, file sizes
Exact same issue here. This is preventing me from using Next in any production site and as I see it, is not fit for purpose.
This issue has been open over 6 months now and it would be nice to know if it isn’t being worked on so I can look for an alternative framework. If it is then why the lack of communication?
It’s a wired behavior (and not well documented), but I stopped dynamic import from ssr and extracted to a client side file it worked.
Before (600KB first load js):
After (80KB first load js):
Want to share my observations on this topic.
I created a demo using the latest 13.5.2 Next.js and App Router:
The application has a dynamic route segment that renders pages based on the configuration from the JSON file. In the application, I implemented 5 base sections used as building blocks for the pages:
I implemented lazy loading for both Heavy Components:
I tested different configurations of the pages, and I found that bundle splitting works only when dynamically loading the components from the client:
I observe the same behavior in newer NextJS with
pages
(not usingapp
directory and server components).dynamic
components are bundled (regardless of{ssr: true}
which is not related to bundling – not sure why it’s mentioned as a potential solution…)Which means
dynamic
does not actually work as a code splitter anymore 😨No, it worked properly in NextJS v12, at least. Was one of the best way to reduce a bundle size. Likely a regression after a rewrite to TurboPack. But it’s kinda weird that nobody else has noticed it. Maybe it’s conditional on something 🤔
Note: the only imports of forementioned components are dynamic, no silly mistakes like static + dynamic imports or reexports from
index.ts
…