The example card on nakodo.app shows what the product knows about one creator, and two parts of it are bar charts: "Voice" (what the comments under their videos are like) and "Range" (which languages the audience writes in). Behind the login, the creator page shows the same two breakdowns for real creators.

Only one of those two pairs loads a charting library.

The public one is a list

// src/app/(marketing)/page.tsx <Bars rows={[ ["Questions", 38, 3, "What the comments are like. 157 of the last 412 ask a question, often which tool to use."], ["Discussion", 27], ["Praise", 29], ["Spam", 6], ]} /> 

Bars is 34 lines in the same file (381 to 414). Each row is an <li> on a three-column grid: a label, a track with a filled span, a percentage.

<ul className="max-w-md space-y-1.5 text-sm"> {rows.map(([label, pct, step, note], i) => ( <li key={label} className="grid grid-cols-[88px_1fr_40px] items-center gap-3"> 

No measurement, no ResponsiveContainer, no client component. The widths are percentages of a grid track, so the browser's layout engine is the chart engine, and the whole thing is in the server-rendered HTML.

The private one is recharts

// src/app/(app)/campaigns/[id]/creators/[channelId]/charts.tsx <ChartContainer config={commentsConfig} className="h-44 w-full"> <BarChart data={data} layout="vertical" margin={{ left: 8, right: 40 }}> <XAxis type="number" hide /> <YAxis type="category" dataKey="label" width={100} tickLine={false} axisLine={false} /> <ChartTooltip cursor={false} content={<ChartTooltipContent hideIndicator />} /> <Bar dataKey="count" radius={1}> 

That file is 90 lines and holds both charts. It is imported by exactly one page: a creator's own page inside a campaign. The data there is real, mixed and uneven: 90 days of uploads where Shorts and long-form need different colours, a views axis that has to be readable at 2,000 and at 2,000,000, and a tooltip that names the video the bar belongs to. That is what a library is for.

Proving the library never reaches a visitor

The landing page's JavaScript is public, so this is checkable from anywhere:

$ curl -s https://nakodo.app/ -o home.html $ grep -o '/_next/static/immutable/chunks/[a-zA-Z0-9._/-]*\.js' home.html | sort -u | wc -l 17 $ for c in $(grep -o '/_next/static/immutable/chunks/[a-zA-Z0-9._/-]*\.js' home.html | sort -u); do curl -s "https://nakodo.app$c" -o "chunk-$(basename $c)"; done $ cat chunk-* | wc -c 1120437 $ grep -l -i recharts chunk-* | wc -l 0 $ grep -l CartesianGrid chunk-* | wc -l 0 $ grep -l 'd3-' chunk-* | wc -l 0 

Seventeen chunks, 1,120,437 uncompressed bytes, and not one byte of recharts or of the d3 modules it builds on. For contrast, the same grep finds sonner in one chunk and posthog in two, so the method does find libraries that are there. Across all 76 URLs in the sitemap, the count of pages whose HTML mentions recharts is also zero.

Nothing clever is doing that. charts.tsx is a client component imported by one page in the (app) route group; the (marketing) group imports nothing from it; Next splits per entry point. The property that keeps it true is dull and greppable: no file under src/app/(marketing) or src/components/content imports @/components/ui/chart. One grep in CI would hold the line, and there is no CI, so for now it is held by the fact that the public pages have no real data to plot.

The wrapper is 372 lines and I use half of it

src/components/ui/chart.tsx came from the shadcn CLI, which makes it part of the one directory in this repo I did not write by hand. It exports six things:

export { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartStyle, } 

The app imports three of them (plus the ChartConfig type). ChartStyle is used inside the file. ChartLegend and ChartLegendContent are imported by nothing anywhere, which is the same finding I hit when I audited that folder a week ago and found four whole unimported modules: an unused export in a CLI-generated file is invisible to this toolchain, because reels/** aside, nothing in it reports on unimported modules.

The style block for a theme that does not exist

ChartStyle is the interesting half. Chart colours in this codebase come from CSS variables, so the config is written like this:

const videosConfig = { views: { label: "Views", color: "var(--seq-5)" }, } satisfies ChartConfig; 

and ChartStyle turns that into a <style> tag per chart instance, one block per theme:

const THEMES = { light: "", dark: ".dark" } as const 
 [data-chart=chart-r1] { --color-views: var(--seq-5); } .dark [data-chart=chart-r1] { --color-views: var(--seq-5); } 

The second block can never match anything. Nothing in this application sets a dark class:

$ grep -rn '"dark"' src --include='*.tsx' --include='*.ts' | wc -l 0 $ grep -rn 'next-themes' src | wc -l 0 

next-themes is in package.json and imported by no file. The stylesheet does carry a full .dark block of 80 declarations, written months ago, which nothing can reach either. So every chart in the app ships its variables twice, and the duplicate is addressed to a theme that has never been switched on. When a config uses color rather than theme, as all of mine do, the two blocks are byte-identical.

Why recharts is pinned and almost nothing else is

Thirty runtime dependencies, six of them pinned to an exact version:

"@duckdb/node-api": "1.5.6-r.1", "@trigger.dev/sdk": "4.7.2", "next": "16.3.7", "react": "19.2.8", "react-dom": "19.2.8", "recharts": "3.8.0", 

The others are carets. Four of the six are runtimes, one is a native addon, and the odd one out is the chart library, for a specific reason: the 372-line wrapper was generated against one major version of it and I did not write that code. A minor bump that changes a tooltip's payload shape would not break a type I control; it would break a file I would then have to read properly to fix. Pinning it means that happens when I choose, not when a lockfile refreshes.

What I would change

  • Delete ChartLegend and ChartLegendContent, and ChartStyle should stop emitting the .dark selector while there is no dark theme to match it.
  • The Voice and Range categories exist twice, hand-written in the marketing page and derived from stored data in the app, with nothing tying them together. The example on the public page can drift from what the product shows, and only reading both files would reveal it.
  • The "no chart library on public pages" property deserves a one-line test rather than a habit.

The list-shaped version is on the home page, the plans and their limits are on pricing, and if you want the arithmetic the real charts sit next to, the public engagement rate calculator does it in the browser with no chart at all.