Skip to content
Drivn
5 min read

Next.js Chart — Recharts Component for App Router

Add a Next.js chart with Drivn: a recharts wrapper with typed config colors, themed tooltip and legend, and server-fetched data in the App Router.

Charts are where the Next.js App Router boundary gets awkward. The data usually lives on the server — a database query, an analytics API, a cached fetch — but every React charting library draws with hooks, refs, and resize observers that only run in the browser. The working pattern is to split the two: fetch in a server component, hand plain rows to a small client component, and let the chart render there.

Drivn's Chart is built for that split. npx drivn add chart writes chart.tsx into your repo with 'use client' on line one, installs recharts as its only npm dependency, and appends five --chart-* color tokens to your globals file. The component itself is thin: the root turns a typed ChartConfig object into --color-{key} CSS variables, wraps your recharts element in a ResponsiveContainer, and shares the config through context so Chart.Tooltip and Chart.Legend show human labels instead of raw data keys.

This guide walks the full App Router path: install, the client chart island, server-side data loading with typed props, theme-aware colors that follow data-theme, and sizing inside dashboard layouts. Every internal detail below is taken from the Chart source the CLI writes. For copy-paste bar, line, area, and pie variants, see React Chart examples.

Install Chart in a Next.js 16 project

Chart installs like every Drivn component: the CLI copies source into your project instead of adding a versioned UI package you have to keep in sync. From the project root run npx drivn add chart. The command expects an initialized project — if you have not run npx drivn@latest create yet, do that first so the CLI knows where your components and globals stylesheet live. It then writes chart.tsx to your components directory, installs recharts with the package manager it detects, and appends the chart token block to your globals file. If that file already defines --chart-1, the token step is skipped with a warning, so re-running the command never duplicates CSS. The CLI reference covers custom install paths and every other flag.

1# from the root of your Next.js 16 project
2npx drivn add chart

Build the chart as a client island

The Chart source starts with 'use client' because it imports recharts and reads its config with React.useContext, and the recharts primitives you pass as children — BarChart, Bar, LineChart — are browser code too. Rather than importing recharts into a page file, put the whole chart in its own client component that receives rows as props. Inside it, wrap exactly one recharts element in <Chart config={config}>; the root types children as a single ReactElement because it hands that element straight to ResponsiveContainer. Chart.Grid draws horizontal lines only, and Chart.XAxis hides tick lines and the axis baseline by default. Declaring the config with satisfies ChartConfig keeps every key checked while preserving the literal shape. Full prop tables live in the Chart docs.

1'use client'
2import { Bar, BarChart } from 'recharts'
3import { Chart, type ChartConfig } from '@/components/ui/chart'
4
5export interface TrafficRow {
6 month: string
7 desktop: number
8 mobile: number
9}
10
11const config = {
12 desktop: { label: 'Desktop', color: 'var(--color-chart-1)' },
13 mobile: { label: 'Mobile', color: 'var(--color-chart-3)' },
14} satisfies ChartConfig
15
16export function TrafficChart({ data }: { data: TrafficRow[] }) {
17 return (
18 <Chart config={config}>
19 <BarChart data={data}>
20 <Chart.Grid />
21 <Chart.XAxis dataKey="month" />
22 <Chart.Tooltip />
23 <Chart.Legend />
24 <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
25 <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
26 </BarChart>
27 </Chart>
28 )
29}

Load data in a server component

With rendering isolated in the client island, the page stays a server component and owns data access. Make the page async, query your database or call an internal API, shape the result into the row interface exported from the chart file, and pass it down as the data prop. The rows cross the server–client boundary as serialized props, so keep them to plain values — strings, numbers, and dates serialize fine, while class instances and functions do not. That last rule matters for charts: a recharts tickFormatter or a custom tooltip formatter has to live inside the client file rather than be passed from the page. Pair the route with a loading.tsx that renders a Skeleton at the chart's height so the layout holds still while the request resolves.

1import {
2 TrafficChart,
3 type TrafficRow,
4} from '@/components/traffic-chart'
5
6async function getTraffic(): Promise<TrafficRow[]> {
7 const res = await fetch('https://api.example.com/traffic', {
8 next: { revalidate: 3600 },
9 })
10 return res.json()
11}
12
13export default async function DashboardPage() {
14 const data = await getTraffic()
15 return (
16 <section className="max-w-3xl">
17 <h1 className="text-lg font-medium">Traffic</h1>
18 <TrafficChart data={data} />
19 </section>
20 )
21}

Theme-aware colors with chart tokens

Colors flow through two layers of CSS variables. The appended token block defines --chart-1 through --chart-5 as a neutral ramp on :root, inverts the ramp under [data-theme="dark"], and maps each one to --color-chart-1 … --color-chart-5 inside @theme inline so Tailwind utilities and inline styles can both read them. Your ChartConfig points each series at one of those, and ChartRoot turns every entry that has a color into a scoped --color-{key} variable set on its wrapper div. Bars and lines then reference var(--color-desktop) rather than a hex value. Because Drivn's theme tokens switch on the data-theme attribute, the chart recolors the moment the theme flips — no theme hook inside the chart file. To brand the palette, edit the token values once; Theming explains the token system.

1/* Drivn Chart Tokens */
2:root {
3 --chart-1: hsl(240 6% 10%);
4 --chart-2: hsl(240 5% 24%);
5 --chart-3: hsl(240 5% 38%);
6 --chart-4: hsl(240 4% 52%);
7 --chart-5: hsl(240 4% 65%);
8}
9
10[data-theme="dark"] {
11 --chart-1: hsl(0 0% 98%);
12 --chart-2: hsl(0 0% 82%);
13 --chart-3: hsl(0 0% 66%);
14 --chart-4: hsl(0 0% 50%);
15 --chart-5: hsl(0 0% 35%);
16}
17
18@theme inline {
19 --color-chart-1: var(--chart-1);
20 --color-chart-2: var(--chart-2);
21 --color-chart-3: var(--chart-3);
22 --color-chart-4: var(--chart-4);
23 --color-chart-5: var(--chart-5);
24}

Sizing charts inside dashboard layouts

The root wrapper ships with flex justify-center w-full aspect-video min-h-[200px] text-xs, so out of the box a chart fills its column at a 16:9 ratio and never collapses below 200 pixels. That suits a full-width overview, but dashboard tiles usually want a fixed height. Pass className to override: the root merges it through cn, so aspect-auto h-64 replaces the ratio cleanly. Inside, ResponsiveContainer measures the wrapper and redraws when it resizes, which means the wrapper needs a parent with a definite width — in a CSS grid or flex row, add min-w-0 to the column so the chart can shrink with it. Placing each chart in a Card gives tiles a consistent frame, and Drivn vs shadcn/ui Chart covers how this setup differs from shadcn.

1<div className="grid gap-4 md:grid-cols-2">
2 <div className="min-w-0">
3 <Chart config={config} className="aspect-auto h-64">
4 <LineChart data={data}>
5 <Chart.Grid />
6 <Chart.XAxis dataKey="month" />
7 <Chart.Tooltip
8 content={<Chart.TooltipContent indicator="line" />}
9 />
10 <Line
11 type="monotone"
12 dataKey="desktop"
13 stroke="var(--color-desktop)"
14 strokeWidth={2}
15 dot={false}
16 />
17 </LineChart>
18 </Chart>
19 </div>
20</div>
Get started

Install Drivn in one command

Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.

npx drivn@latest create

Requires Node 18+. Works with npm, pnpm, and yarn.

Enjoying Drivn?
Star the repo on GitHub to follow new component releases.
Star →

Frequently asked questions

Keep recharts out of the page file. The Chart source is marked 'use client', and the recharts elements you nest inside it — BarChart, Bar, Line — run in the browser as well. Put the chart in a small client component that takes rows as props, then render that component from your async server page. Data fetching stays on the server; drawing happens on the client.

Both Chart.TooltipContent and Chart.LegendContent read the config through the useChart hook and look up each series by its name or dataKey. When the config entry has a label, that label is shown; otherwise the raw key is used. Numeric values are formatted with toLocaleString(), so large numbers get separators without a custom formatter.

Any single recharts chart element: BarChart, LineChart, AreaChart, PieChart, and the rest. The root passes its one child to ResponsiveContainer and adds styling hooks for grid lines, axis ticks, and pie sectors. Chart.Grid and Chart.XAxis wrap cartesian primitives, so pie and donut charts simply leave them out and keep Chart.Tooltip and Chart.Legend.

It comes from the useChart hook, which throws when Chart.TooltipContent or Chart.LegendContent render without a Chart root above them. The root provides the config context those pieces need for labels and icons. Move the tooltip or legend inside the recharts element that sits under <Chart config={config}> and the error disappears.