# How you import Chart.js decides how many kilobytes the visitor downloads

> The same chart, four import strategies. Measured in this site's own Astro + Rolldown build, not in a synthetic bundler.

- Kind: Measurement
- Question: In a real production build, what is the difference between `chart.js/auto` and a selective `Chart.register()` — in kilobytes?
- Finding: Selective registration saves 9.7 kB gzip over `chart.js/auto` (67.8 → 58.1 kB, 14.3%). The larger drop is not in the library core but in leaving unused controllers out: a page that registers only the bar chart falls to 46.0 kB — two thirds of auto.
- Method: Four separate `npx astro build` runs against the same repository, the same chart component and the same content file; only the import/register lines of `chart-render.ts` changed between runs. What is measured is the `gzip -9` size of the `dist/_astro/chart-render.*.js` chunk Rolldown emits. The build is deterministic, so one run suffices: identical input produced identical bytes, and each of the four strategies was run twice to confirm.
- Measured on: 2026-08-19
- Confidence: High confidence
- Programme: Frontend performance
- Environment: Astro 7.1.3 · Chart.js 4.5.1 · Bundler Rolldown (Astro 7 default) · Node 24.18.0 · OS macOS (Darwin 25.6) · Compression gzip -9
- Technologies: Chart.js, Astro, Rolldown, Preact
- Published: 2026-08-19
- Source: https://www.muhammetsafak.com.tr/en/research/chartjs-import-stratejisi/
- Language: en-US
- Author: Muhammet Şafak

---
For years this site had one rule: **no required client-side JavaScript.** I
broke it deliberately when opening the Research section — a multi-series
measurement is not readable in a chart without tooltips. But the difference
between "broke it" and "cared about it" shows up in whether you measure the
bytes you ship.

Chart.js documents two ways. The short one:

```ts
import Chart from 'chart.js/auto';
```

The long one:

```ts
import { Chart, BarController, BarElement, CategoryScale, LinearScale } from 'chart.js';
Chart.register(BarController, BarElement, CategoryScale, LinearScale);
```

Both work. My question was whether the difference is a measurable number in a
real build, or a micro-optimisation.

## What I measured

I did not set up a synthetic bundler — what I wanted to know is not "how small
can Chart.js get" but **how much this repository ships.** So all four runs went
through the site's own `astro build`: the same MDX entry, the same chart
component, the same Preact island. The only thing that changed between runs was
the first ten lines of `src/components/research/chart-render.ts`.

**Gzip size of the Chart.js chunk, by import strategy**

All four draw the same chart. The difference is only which controllers and plugins enter the bundle.

Kaynak: astro build output, dist/_astro/chart-render.*.js, gzip -9

|  | chart.js/auto | bar+line+tooltip+filler | bar+line, no plugins | bar only |
| --- | --- | --- | --- | --- |
| gzip | 67.8 kB | 58.1 kB | 51.2 kB | 46 kB |

With raw (uncompressed) sizes alongside:

| Strategy | Raw | gzip | vs. auto |
| --- | --- | --- | --- |
| chart.js/auto | 204,965 B | 69,466 B | — |
| bar + line + Tooltip + Filler | 172,313 B | 59,538 B | −9,928 B (14.3%) |
| bar + line, no plugins | 149,793 B | 52,378 B | −17,088 B (24.6%) |
| bar only | 134,868 B | 47,114 B | −22,352 B (32.2%) |

Raw and gzip move in the same direction but not at the same rate: compression was already squeezing part of the code that got deleted.

## What the number says

Selective registration saved **9.7 kB gzip** over `auto`. That is the real
figure behind the documentation's "tree-shaking is supported" — and on its own
it is modest: 14.3%.

The actual information is in rows two and three. Dropping the `Tooltip` and
`Filler` plugins takes another 7.2 kB; a page that never draws a line chart
saves 5.3 kB more. In other words, **most of the cost is not in the library
core but in chart types you never draw.** Which is exactly what `auto` does: it
registers all of them.

> **Sonuç**
>
> The rule that falls out: escaping `auto` is a small win by itself. What makes
> the win large is knowing which chart type the page **actually draws** and never
> bundling the rest. This site uses bars and lines together and I am not giving up
> tooltips — so I stop at 58.1 kB and cannot reach 46.0.

## What the whole page pays

The Chart.js chunk alone is not meaningful; when the island hydrates, the
visitor downloads:

| Chunk | gzip | What |
| --- | --- | --- |
| chart-render | 58.1 kB | Chart.js + theme bridge |
| preact | 4.9 kB | runtime core |
| hooks | 0.8 kB | useRef / useEffect |
| client | 0.8 kB | @astrojs/preact client bridge |
| ChartIsland | 0.6 kB | the component itself |
| Total | 65.3 kB |  |

This is the island cost of a research page with one chart. The `signals.module` chunk is emitted by the build but the page never requests it — @astrojs/preact loads it only when `data-preact-signals` is present.

The framework side is **under 10%** of the total (6.3 kB). That does not mean
choosing Preact over React would not have mattered here — quite the opposite:
`react-dom` alone approaches the size of Chart.js. But that is a separate
measurement and a separate record.

> **Sınır**
>
> The measurement is the size of the **build output**, not the visitor's real
> download time. The chunks sit under `/_astro/` with a one-year `immutable`
> cache and are requested only when the chart scrolls into view (`client:visible`),
> so beyond the first visit these bytes are not paid again. I also measured gzip —
> Cloudflare serves brotli to most browsers, where the figure will be lower.

## What I did

`chart-render.ts` opens with these lines and never touches the `auto` entry:

```ts
import {
  BarController, BarElement, LineController, LineElement, PointElement,
  CategoryScale, LinearScale, Tooltip, Filler,
} from 'chart.js';

Chart.register(
  BarController, BarElement, LineController, LineElement, PointElement,
  CategoryScale, LinearScale, Tooltip, Filler,
);
```

`Legend` is not on the list: the legend is rendered as HTML on the server. That
way it is styled with the site's own tokens and it stays in place even if no
JavaScript runs at all — the same reasoning as the "Data table" disclosure
below every chart on this page.
