Next.js has become the go-to framework for building modern web apps. But as projects grow, it’s easy to fall into messy structures, performance issues, and technical debt.
This guide covers best practices for Next.js development — from project structure to data fetching, state management, testing, security, and deployment.
For App Router (Next.js 13+):
/app
/dashboard
/page.tsx
/layout.tsx
/blog
/[slug]
/page.tsx
/api
/route.ts
/components
/lib
/hooks
/styles
/app→ routes, layouts, server & client components/components→ reusable UI components/lib→ utilities (db, auth, API wrappers)/hooks→ custom React hooks/styles→ global styles, Tailwind configs
- Keep related code together:
/components
/Button
Button.tsx
Button.test.tsx
- Server Components (RSC) reduce bundle size, remove the need for
useEffectin many cases, and improve performance.
// Server Component (default)
export default async function Page() {
const data = await getData();
return <div>{data.title}</div>;
}- Add
"use client"at the top only for interactive components.
"use client";
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}- Split large UI chunks into smaller parts.
- Favor composition over props explosion.
export default async function Page() {
const res = await fetch("https://api.example.com/data", {
next: { revalidate: 3600 }, // ISR cache for 1 hour
});
const data = await res.json();
return <div>{data.title}</div>;
}- Use
revalidatefor ISR. - Use
cache: "no-store"for always-fresh data.
- For server state → prefer React Query, SWR, or Next.js
fetch. - For client state → use Zustand or Jotai instead of Redux unless needed.
- Consolidate queries in Server Components.
- Use batching (e.g., GraphQL or tRPC).
- Use
<Image />for automatic image optimization. - Use
next/fontfor fonts → avoids layout shifts. - Dynamic imports for heavy components:
import dynamic from "next/dynamic";
const Chart = dynamic(() => import("./Chart"), { ssr: false });- Bundle Analysis → Find large dependencies:
npm install @next/bundle-analyzer- Use
/app/api/route.tsfor backend logic. - Keep business logic in
/libso it’s reusable.
// app/api/users/route.ts
import { db } from "@/lib/db";
export async function GET() {
const users = await db.user.findMany();
return Response.json(users);
}- Use
runtime: "edge"for lightweight APIs (e.g., auth, geolocation).
export const config = { runtime: "edge" };- Store secrets in environment variables (
.env.local). - Use HTTP-only cookies for authentication tokens.
- Sanitize user input (e.g., with
DOMPurify). - Use Helmet headers (or
next-safe) for CSP, XSS protection.
npm install next-safeimport { createMiddleware } from "next-safe";
export default createMiddleware({
contentSecurityPolicy: { "script-src": ["'self'"] },
});// tsconfig.json
"strict": true,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": trueimport { render, screen } from "@testing-library/react";
import Button from "./Button";
test("renders button", () => {
render(<Button>Click</Button>);
expect(screen.getByText("Click")).toBeInTheDocument();
});npx playwright test- Use Next.js Metadata API:
export async function generateMetadata() {
return {
title: "My Next.js Site",
description: "Best practices in action",
};
}- Add OpenGraph & Twitter cards.
- Ensure semantic HTML & ARIA labels for accessibility.
- Deploy on Vercel for ISR, CDN caching, and edge functions.
- Use Preview Deployments for QA.
- Enable logging & monitoring (Sentry, Logtail).
- ESLint + Prettier for consistent code.
- Husky + lint-staged for pre-commit checks.
- CI/CD pipelines (GitHub Actions / Vercel Integrations).
// package.json
"lint-staged": {
"*.ts": "eslint --fix",
"*.tsx": "eslint --fix"
}✅ Organize project structure clearly ✅ Prefer Server Components, use Client only when needed ✅ Use ISR & caching for data fetching ✅ Optimize images, fonts, and bundles ✅ Secure secrets & user data ✅ Write tests (unit + E2E) ✅ Use Metadata API for SEO ✅ Deploy with CDN + monitoring ✅ Enforce coding standards with ESLint/Prettier
By following these practices, your Next.js apps will be faster, more secure, easier to maintain, and ready for scale 🚀