Forms and Validation
Robust Form Management and Data Validation
The Next.js Boilerplate provides a production-ready foundation for handling user input with absolute type safety. By integrating React Hook Form with Zod, the system eliminates the common frustrations of manual state management and inconsistent data validation.
This architecture ensures that your application remains scalable and resilient, providing immediate feedback to users while maintaining strict data integrity on the server.
Integrated Tech Stack
Our form handling strategy is built on three pillars:
- React Hook Form: For high-performance, flexible form state management with minimal re-renders.
- Zod: For schema-based validation that serves as the "single source of truth" for your data shapes.
- TypeScript: To provide end-to-end type safety from the UI component down to the database layer.
Key Value Propositions
1. Unified Validation Logic
Stop duplicating logic. Define your validation rules once in a Zod schema and reuse them across your client-side forms and your server-side API routes. This ensures that the data being sent from the browser exactly matches what the server expects.
2. Frictionless User Experience
With built-in support for asynchronous validation and immediate error reporting, users receive instant feedback as they interact with your forms. The boilerplate handles error state transitions automatically, allowing you to focus on building beautiful UI components with Tailwind CSS.
3. Full Type Safety
Because the validation schemas are used to infer TypeScript types, you gain autocompletion throughout your IDE. If you change a field name in your schema, TypeScript will immediately flag every component or API handler that needs to be updated.
Implementation Example: The Counter Form
The boilerplate includes a practical implementation of these concepts within the Counter feature. This demonstrates how to bridge the gap between a client-side interaction and a secure API update.
Defining the Schema
Validation logic is centralized in the validations directory, ensuring it can be imported by both the frontend and the backend.
// src/validations/CounterValidation.ts
import { z } from 'zod';
export const CounterValidation = z.object({
increment: z.coerce.number().int().min(1).max(100),
});
Secure API Protection
On the server side, the boilerplate uses these schemas to guard API routes. In the example below, the PUT request is automatically validated. If the payload doesn't match the CounterValidation schema, the server returns a 422 Unprocessable Entity error before any database operations occur.
// src/app/[locale]/api/counter/route.ts
import { CounterValidation } from '@/validations/CounterValidation';
import { NextResponse } from 'next/server';
import * as z from 'zod';
export const PUT = async (request: Request) => {
const json = await request.json();
const parse = CounterValidation.safeParse(json);
if (!parse.success) {
// Returns formatted validation errors to the client
return NextResponse.json(z.treeifyError(parse.error), { status: 422 });
}
// Data is now guaranteed to be a valid number
const { increment } = parse.data;
// ... proceed with database logic
};
Best Practices for Your Project
To maintain the high standards of the boilerplate as you scale, we recommend following these patterns:
- Schema Colocation: Keep your Zod schemas in the
src/validationsfolder to facilitate sharing between the App Router (Server Components/Actions) and Client Components. - Error Handling: Use the provided
z.treeifyErrorutility in your API routes. This transforms complex Zod error objects into a clean, hierarchical structure that is easy for the frontend to map back to specific form fields. - Internationalization: Combine validation with
next-intlto provide localized error messages, ensuring your forms are accessible and user-friendly for a global audience.