Topic 16 of 47
TypeScript Patterns
Overview
TypeScript is the standard for Next.js — it provides type safety for route params, search params, metadata, and fetch responses. Knowing the correct types prevents runtime errors.
Syntax
typescript
// Page props — TypeScript types
interface PageProps {
params: { slug: string }; // URL path params
searchParams: { q?: string }; // Query string params
}
export default async function Page({ params, searchParams }: PageProps) {
const { slug } = params;
const query = searchParams.q ?? "";
}
// Metadata type
import { Metadata } from "next";
export const metadata: Metadata = {
title: "My Page",
};
// generateMetadata with types
export async function generateMetadata(
{ params }: PageProps
): Promise<Metadata> {
return { title: params.slug };
}
// Server Action with types
async function saveData(prevState: FormState, formData: FormData): Promise<FormState> {
"use server";
return { success: true };
}Common Pitfalls
- searchParams in page components are always strings — use z.coerce.number() or parseInt() to convert number params.
- params can be async in Next.js 15 — await params before accessing properties.
- Interview tip: Use 'satisfies' operator for metadata: export const metadata = { ... } satisfies Metadata; — gives type checking without losing literal types.
Real-World Example
Fully typed Next.js page with server action and form
example
typescript
// types/actions.ts
export interface ActionState<T = void> {
success: boolean;
data?: T;
error?: string;
fieldErrors?: Record<string, string[]>;
}
// actions/products.ts
"use server";
import { z } from "zod";
const Schema = z.object({
name: z.string().min(2),
price: z.coerce.number().positive(),
});
export async function createProduct(
prevState: ActionState,
formData: FormData
): Promise<ActionState> {
const parsed = Schema.safeParse(Object.fromEntries(formData));
if (!parsed.success) {
return {
success: false,
fieldErrors: parsed.error.flatten().fieldErrors,
};
}
await db.products.create(parsed.data);
revalidatePath("/products");
return { success: true };
}