Joonas' blog

Type-safe prop passing in Next.js server components

published

Recently I have relied quite heavily on a Next.js app directory architecture, where data is fetched from a database in the server component and then passed to client for further clientside processing or display.

app/products/[id]/
- page.tsx (server component)
- Product.tsx (client component with clientside-enriched visualization)

As nice as this is to move some processing and rendering to serverside and thus achieve more SEO- and slow-connection-friendly HTML output, it doesn’t directly offer any solutions for passing type-safe data to client.

Here I describe the pattern I’ve found simple and straightforward for type-safely passing data to client and separating data fetching and usage concerns.

Data pattern

The pattern involves adding a new data.ts file, which is responsible for 1. serverside data fetching and 2. providing types for fetched data.

app/products/[id]/
- page.tsx
- data.ts (serverside data loading utility and shared type for data result)
- Product.tsx

data.ts

import "server-only";
import { supaClient } from "@/app/supaClient";
export function load(productId: string) {
return supaClient()
.from("products")
.select("name, price")
.eq("id", productId);
}
export type Data = Awaited<ReturnType<typeof load>>;

page.tsx

import { Product } from "./Product";
import { load } from "./data";
export default async function Page({ params: { id } }) {
const data = await load(id);
return (
<div className="page-panel">
<p>This page contains visualization for {data.name}</p>
<Product id={id} data={data} />
</div>
);
}

Product.tsx

"use client";
import type { Data } from "./data";
export function Product({ id, data }: { id: string; data: Data }) {
const formattedPrice = new Intl.NumberFormat(
undefined,
{ style: 'currency', currency: 'USD' }
).format(data.price);
return (
<section>
<h2>{data.name}</h2>
<p>price: {formattedPrice}</p>
</section>
);
}

This pattern both simplifies implementation of page.tsx and makes the app more type-safe with up-to-date and non-duplicated type definitions for page data. Additionally, it works well with Next.js’ own data preload pattern.