PitchKit
Guides

Next.js & SSR

Server-rendered pitches under the App Router, and the one boundary rule to know.

@pitchkit/react server-renders to real SVG — verified against renderToString in the test suite and against a real Next.js App Router app (examples/react-nextjs/ in the repo). The initial HTML response contains the full pitch and its marks; hydration attaches interactivity without re-drawing anything.

The one rule: originate the tree in a client component

Every layer takes accessor functions as props (x={(p) => p.x}), and React Server Components cannot pass functions as props across the server → client boundary. Compose your <Pitch> tree inside a "use client" component, and render that from your page:

// app/ShotMapPanel.tsx
"use client";

import { Pitch, Scatter } from "@pitchkit/react";
import { shots } from "./data";

export function ShotMapPanel() {
  return (
    <Pitch type="statsbomb">
      <Scatter data={shots} x={(s) => s.x} y={(s) => s.y} />
    </Pitch>
  );
}
// app/page.tsx — stays a Server Component
import { ShotMapPanel } from "./ShotMapPanel";

export default function Page() {
  return <ShotMapPanel />;
}

Putting the <Pitch> tree directly in a Server Component page fails next build with "Functions cannot be passed directly to Client Components" — that's this rule, not a PitchKit limitation.

"use client" governs hydration and prop serialization, not whether SSR happens — the panel above is still fully server-rendered into the initial HTML.

Heatmaps are client-only by design

<Heatmap> paints to a <canvas>, which has no server-renderable content — it renders nothing on the server and paints after hydration. The pitch and any SVG layers around it still SSR normally.

Data fetching

Fetch on the server as usual and pass plain data down — arrays and objects serialize fine across the boundary; only functions don't. Accessors live in the client component, next to the JSX that uses them.

On this page