Setting Up TypeScript
Next.js has built-in TypeScript support:
npx create-next-app@latest my-app --typescript
Essential Type Patterns
API Response Types
// Define the expected shape
interface ApiResponse<T> {
data: T;
success: boolean;
error?: string;
timestamp: number;
}
interface User {
id: string;
name: string;
email: string;
}
// Type the response
async function fetchUser(id: string): Promise<ApiResponse<User>> {
const res = await fetch(/api/users/${id});
return res.json();
}
Form Types with Zod
import { z } from 'zod';
const SignupSchema = z.object({
name: z.string().min(2, 'Name too short'),
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Password must be 8+ chars'),
confirmPassword: z.string(),
}).refine(data => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword'],
});
type SignupForm = z.infer<typeof SignupSchema>;
Component Props with Generics
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
// Usage
<List
items={users}
renderItem={user => <span>{user.name}</span>}
keyExtractor={user => user.id}
/>
Strict Mode Benefits
Enable strict mode in tsconfig.json:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}
Conclusion
TypeScript catches bugs at compile time, reducing runtime errors. Invest in proper typing - your future self will thank you.
npx create-next-app@latest my-app --typescript
// Define the expected shape
interface ApiResponse<T> {
data: T;
success: boolean;
error?: string;
timestamp: number;
}
interface User {
id: string;
name: string;
email: string;
}
// Type the response
async function fetchUser(id: string): Promise<ApiResponse<User>> {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
import { z } from 'zod';
const SignupSchema = z.object({
name: z.string().min(2, 'Name too short'),
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Password must be 8+ chars'),
confirmPassword: z.string(),
}).refine(data => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword'],
});
type SignupForm = z.infer<typeof SignupSchema>;
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
// Usage
<List
items={users}
renderItem={user => <span>{user.name}</span>}
keyExtractor={user => user.id}
/>
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}