vercel / next.js

The React Framework
https://nextjs.org
MIT License
122.91k stars 26.28k forks source link

Client components make bundle size huge #67386

Open pepew-le-boss opened 3 days ago

pepew-le-boss commented 3 days ago

Link to the code that reproduces this issue

https://github.com/pepew-le-boss/nextjs-bundle-size-icon-component

To Reproduce

  1. clone the repo
  2. read the readme.md
  3. build the app
  4. look at the client.html generated by the bundle analyzer

Current vs. Expected behavior

The bundle size should be optimized but that's not the case

Provide environment information

Operating System:
  Platform: win32
  Arch: x64
  Version: Windows 11 Enterprise
  Available memory (MB): 14274
  Available CPU cores: 8
Binaries:
  Node: 18.18.0
  npm: N/A
  Yarn: N/A
  pnpm: N/A
Relevant Packages:
  next: 14.2.3 // There is a newer version (14.2.4) available, upgrade recommended!
  eslint-config-next: 14.2.3
  react: 18.3.1
  react-dom: 18.3.1
  typescript: 5.4.5
Next.js Config:
  output: N/A
 ⚠ There is a newer version (14.2.4) available, upgrade recommended!
   Please try the latest canary version (`npm install next@canary`) to confirm the issue still exists before creating a new issue.
   Read more - https://nextjs.org/docs/messages/opening-an-issue

Which area(s) are affected? (Select all that apply)

create-next-app, Performance, Webpack

Which stage(s) are affected? (Select all that apply)

next build (local)

Additional context

Repo here: https://github.com/pepew-le-boss/nextjs-bundle-size-icon-component

When building the app with the bundle analyzer, under certain conditions the Icon.tsx chunk (in the client.html) has a huge parsed size (to better view the chunk choose the "Stat" view). Here's the explanation:

You can find in the code an Icon.tsx component that looks like this:

export const Icon = {
  ICON_NAME_1: (props: IconProps) => (
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" {...props}>
      ...
    </svg>
  ),
  ICON_NAME_2: (props: IconProps) => (
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" {...props}>
      ...
    </svg>
  ),
  ...
}

Then it can be used like this:

<Icon.ICON_NAME_1 style={{ width: 100, height: 100, fill: "orange" }} />

When used is server components, there are no problems.

When imported in a client component, a chunk Icon.tsx is created with an optimized parsed size (stat size: 195kB / parsed size: 1kB).

For example:

"use client"

import { Icon } from "@/components/Icon"

export function ClientComponent() {
  return (
    <Icon.ICON_NAME_1 style={{ width: 100, height: 100, fill: "purple" }} />
  )
}

And then used like this:

export default function Home() {
  return (
    <main>
      <ClientComponent />
    </main>
  );
}

However, when at least another client component that is importing Icon.tsx is used anywhere in the app, the bundle analyzer shows that the Icon.tsx chunk is not optimized at all (stat size: 195kB / parsed size: 168kB).

For example:

export default function Home() {
  return (
    <main>
      <ClientComponent />
      <ClientComponent2 />
    </main>
  );
}

Is it a normal behavior ? Is there a way to fix that ?