Open leo-cheron opened 1 year ago
Duplicate of the - https://github.com/vercel/next.js/issues/49454
Expected behavior: When server components are imported via next/dynamic, shouldn't the components be split into chunks on the server? At least the CSS?
The issue is that the CSS of all components is bundled into a single CSS file. This naturally leads to performance losses in PageSpeed Insights, especially in larger projects.
page.js:
import dynamic from 'next/dynamic';
const Page1 = dynamic(() => import('@/components/Page1'));
const Page2 = dynamic(() => import('@/components/Page2'));
const Page = ({ params: { pagename: pagenameArr } }) => {
const pagename = pagenameArr?.[0];
if (pagename === 'page1') return <Page1 />;
if (pagename === 'page2') return <Page2 />;
};
export default Page;
component Page1:
import styles from "./Page1.module.css";
const Page1 = () => {
return <div className={styles.page1}>Page 1</div>;
};
export default Page1;
Page1.module.css
.page1 {
background-color: #00ffff;
}
component Page2:
import styles from "./Page2.module.css";
const Page2 = () => {
return <div className={styles.page2}>Page 2</div>;
};
export default Page2;
Page2.module.css
.page1 {
background-color: #ff00ee;
}
When I open Page1 in the browser, a CSS file (efe31bd8f307aac7.css) is loaded that contains styling from both components:
.Page2_page2__PYcEf{background-color:#f0e}
.Page1_page1__9tvyj{background-color:#0ff}
I've been experiencing a similar issue and read the duplicate, but it was closed, and nothing was done about it. It makes it difficult to understand how to code-split in NextJS 14.
The claim was that it would be "automatic" and that any client component imported inside a server component that suspends and streams progressively would not increase FirstLoadJS, but I'm finding that this isn't true at all.
Instead, all client code is sent to the browser at the same time, regardless of whether it renders initially, unless you code-split inside a client component, which introduces an unnecessary round-trip. Nothing you can do on the server, whether using lazy()
or dynamic()
(with ssr: false
or true
), can stop this.
Our biggest problem is not necessarily the splitting of JS files (that works as long as you integrate another component that then loads again via next/dynamic), but rather CSS files that become far too large and cannot be split. It would be sensational if an intelligent solution were sought for this.
I am experiencing exactly the same problem.
Using Next 14 (canary), I have a catch-all route in /src/app/[...slug]
as we are using a CMS where we render a page based on the slug. I've already wrapped my dynamic components in a client component, as mentioned in duplicate https://github.com/vercel/next.js/issues/49454, but the CSS is just being bundled in a few big CSS files.
This means that dedicated client-components (such as form inputs, checkboxes, modals, etc), which are dynamically imported from a client wrapper, still have their CSS bundled into the big CSS files, even though those components are not used on the page at all. Next.js is supposed to be all about performance, but how can you benefit from reduced client-side javascript if you have a huge render-blocking CSS destroying your performance metrics.
Progress on this topic would be great. I think there must be the option to split CSS files (rendering blocking). We have big losses in Pagespeed Insights because of this.
So I've done a bit more research on this, and managed to get the CSS and JS code-splitting working, but only when used in very specific circumstances.
First and foremost, I have realised that if you have a /components
folder and in that folder an /index.ts
where you export your components (for example export * from './Button'
, so you can use import { Button } from '@components'
, code splitting does not work. Maybe this is by design, as the compiler is not sure which components you might need from the components dir and therefore just includes them all? This was a bit of a bummer, because organising your code like this works really well in larger projects.
So the JS file and CSS is lazy loaded, only if the following conditions are met:
dynamic
import from within a client component ('use client'
) - So essentially you are going to end up creating endless extra client wrappers to lazy load components, described as a 'solution' in https://github.com/vercel/next.js/issues/49454import { Button } from '@components/Button/Button'
. , you cannot import from something like an index.ts
in your components folderIn relation to point 3, I've found that if you'd have a server component like this:
const TextinputWithHeavyAnimationLib = dynamic(() => import('./path/to/lib))
const FormWrapper = ({ showForm }) => {
return (
<div>
{showForm && <TextinputWithHeavyAnimationLib /> }
</div>
)
}
Then it just loads the Textinput component, including CSS which will be bundled in the main CSS file. You have to wrap the Textinput component with another client wrapper, which then in turn imports the actual component.
Tbh it's a real pain to have to create these workarounds, but if performance is absolutely paramount for your project then I hope these steps might help someone who's stuck like I was.
@leerob any update here? Are we planning to do anything to solve the code-splitting issue on server-side components inside the APP Directory?
Also anything on an option to split CSS files (rendering blocking)
Link to the code that reproduces this issue or a replay of the bug
https://github.com/leo-cheron/nextjs-issue-dynamic
To Reproduce
Dynamically multiple server of client components from a server page / component:
Where ServerComponentA & B will import respectively ClientComponentA & ClientComponentB like below:
ClientComponentA being just a large SVG:
Current vs. Expected behavior
According to the documentation, If you dynamically import a Server Component, only the Client Components that are children of the Server Component will be lazy-loaded - not the Server Component itself.
By running bundle analyze, we see that both
ClientComponentA
&ClientComponentB
are added to same page chunk, where we'd expect them to be split in two lazy loaded separated chunks. This issue defeats the purpose of dynamic loading and will prevent any client module from being loaded on demand.I also tried to dynamically import
ClientComponentA
fromServerComponentA
without success. Chunk splitting would only work when dynamic import is used from a client component (which we don't want here).Build prod demo can be found here
Verify canary release
Provide environment information
Which area(s) are affected? (Select all that apply)
App Router
Additional context
Any production build is concerned.