Next.js
Fullstack React apps with App Router, server components, and server actions for Next.js 13-14.
When to Use
- React apps with SSR/SSG
- API routes or middleware
- Fullstack React deployment
Don’t use for:
- Static sites without React (use Astro)
- Backend-only APIs (use Express/Hono)
- Non-React frontends (use SvelteKit/Nuxt)
Critical Patterns
✅ REQUIRED: Server Components vs Client Components
Server components by default. Add "use client" only for browser APIs, state, or events.
// CORRECT: server component fetches data directly (no directive needed)
export default async function UsersPage() {
const users = await db.getUsers();
return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}
// WRONG: adding "use client" just to render data, then using useEffect + fetch
✅ REQUIRED: File-Based Routing with layout.tsx
layout.tsx for shared UI persisting across child routes without re-render.
// app/dashboard/layout.tsx wraps all /dashboard/* pages
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex">
<Sidebar />
<main>{children}</main>
</div>
);
}
✅ REQUIRED: Server Actions
"use server" for mutations without API routes.
"use server";
import { revalidatePath } from "next/cache";
export async function createUser(formData: FormData) {
const name = formData.get("name") as string;
await db.insertUser({ name });
revalidatePath("/users");
}
✅ REQUIRED: Data Fetching in Server Components
Fetch directly in async server components. Next.js deduplicates and caches fetch.
async function getProducts() {
const res = await fetch("https://api.example.com/products", {
next: { revalidate: 60 },
});
return res.json();
}
export default async function ProductsPage() {
const products = await getProducts();
return <ProductList items={products} />;
}
✅ REQUIRED: Metadata API
Export metadata or generateMetadata for SEO (no manual <head>).
// Static metadata
export const metadata = { title: "Dashboard", description: "User dashboard" };
// Dynamic metadata based on params
export async function generateMetadata({ params }: { params: { id: string } }) {
const product = await getProduct(params.id);
return { title: product.name, description: product.summary };
}
Decision Tree
Needs browser APIs or state?
→ Add "use client" directive
Fetching data for display?
→ Use async server component, not useEffect
Form submission or mutation?
→ Use a server action with "use server"
Shared layout across routes?
→ Create a layout.tsx in the parent segment
Dynamic page title?
→ Export generateMetadata function
Protecting routes?
→ Use middleware.ts at the project root
Periodic data refresh?
→ fetch with next: { revalidate: N }
On-demand cache clear?
→ Call revalidatePath() or revalidateTag()
Example
// app/posts/page.tsx - server component with client island
import LikeButton from "./like-button";
async function getPosts() {
const res = await fetch("https://api.example.com/posts", {
next: { revalidate: 120 },
});
return res.json() as Promise<{ id: string; title: string }[]>;
}
export const metadata = { title: "Blog Posts" };
export default async function PostsPage() {
const posts = await getPosts();
return (
<ul>
{posts.map((p) => (
<li key={p.id}>{p.title} <LikeButton postId={p.id} /></li>
))}
</ul>
);
}
Edge Cases
- Client boundaries:
"use client"makes component + imports client-side; push down tree. - Serialization: Server→client props must be serializable (no functions/Dates/classes).
- Waterfall fetches: Sequential
awaitcreates waterfalls; usePromise.all(). - Middleware limits: Edge Runtime only; no Node.js APIs (
fs, DB drivers). - Revalidation conflicts: Mixed
revalidatevalues use lowest; set deliberately.
Checklist
- Components are server components by default;
"use client"only added when required - Data fetching uses async server components, not client-side useEffect
- Layouts are used for persistent shared UI across route segments
- Server actions handle form submissions and mutations
- Metadata is set via
metadataexport orgenerateMetadatafunction -
middleware.tshandles auth redirects and rewrites - Fetch calls include
next: { revalidate }orcacheoptions - Client component boundaries are pushed as low in the tree as possible