A comprehensive reference for Next.js 16 concepts with working examples. Use this as a boilerplate or quick lookup for common patterns.
Stack: Next.js 16.1.1 β’ React 19 β’ TypeScript β’ Tailwind CSS 4
π¬ Learned from: Next.js 16 Crash Course on YouTube
- Quick Start
- Project Structure
- Core Concepts
- Data Fetching
- API Routes
- Caching Strategies
- Metadata & SEO
- Performance Features
- Build Adapters
# Install dependencies
npm install
# Run development server (with Turbopack)
npm run dev
# Build for production
npm run build
# Start production server
npm startapp/
βββ layout.tsx # Root layout (required)
βββ globals.css # Global styles
βββ loader.tsx # Loading UI component
βββ error.tsx # Error boundary
βββ global-error.tsx # Global error boundary
β
βββ (root)/ # Route group with Navbar/Footer layout
β βββ layout.tsx # β Wraps: Navbar + {children} + Footer
β βββ page.tsx # β /
β βββ about/page.tsx # β /about (throws error for demo)
β βββ books/page.tsx # β /books (fetches from API)
β βββ client-fetcher/ # β /client-fetcher
β βββ page.tsx
β
βββ (dashboard)/ # Route group with Sidebar layout
β βββ layout.tsx # β Wraps: Sidebar + {children}
β βββ dashboard/page.tsx # β /dashboard
β
βββ api/ # API routes
βββ route.ts # β GET /api
βββ db.ts # Mock database
βββ books/
βββ route.ts # β GET, POST /api/books
βββ [id]/route.ts # β PUT, DELETE /api/books/:id
components/
βββ hello.tsx # Client component example
βββ clientDataFetcher.tsx # Client-side data fetching
βββ dataFetcher.tsx # Server component data fetching
| Aspect | Server Components | Client Components |
|---|---|---|
| Rendering | Server β sends HTML | Browser |
| Directive | Default (none needed) | 'use client' at top |
| Use for | Data fetching, secrets, DB access | Interactivity, forms, events |
| Best practice | Use by default | Use only when needed |
π Examples:
- Server:
app/(root)/page.tsx- logs on server - Client:
components/hello.tsx- logs on browser
// Server Component (default)
const ServerPage = () => {
console.log("Runs on server"); // Check terminal
return <div>Hello</div>;
};
// Client Component
("use client");
const ClientComponent = () => {
console.log("Runs on browser"); // Check browser console
return <button onClick={() => alert("Hi")}>Click</button>;
};π‘ Rule: Use Server Components until you need client interactivity and get an error.
Route groups (folder) organize routes without affecting URL and allow different layouts.
| File | URL | Layout |
|---|---|---|
app/(root)/page.tsx |
/ |
Navbar + Footer |
app/(root)/about/page.tsx |
/about |
Navbar + Footer |
app/(dashboard)/dashboard/page.tsx |
/dashboard |
Sidebar |
π Files:
- Root group layout:
app/(root)/layout.tsx - Dashboard group layout:
app/(dashboard)/layout.tsx
β οΈ You must keep a rootlayout.tsxin/appdirectory.
| File | Scope | Location |
|---|---|---|
error.tsx |
Catches errors in that route segment | app/(root)/error.tsx |
global-error.tsx |
Catches root layout errors | app/global-error.tsx |
"use client"; // Required!
const Error = ({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) => {
return (
<div>
<h1>Something went wrong!</h1>
<button onClick={reset}>Try again</button>
</div>
);
};π Test it: Visit /about to see error boundary in action.
Create loading.tsx for automatic loading states during navigation/data fetching.
π Example: app/loader.tsx
const Loading = () => <div>Loading...</div>;
export default Loading;Also supports:
unauthorized.tsxandforbidden.tsxfor auth states.
Traditional React approach - renders loading state first, then fetches.
π File: components/clientDataFetcher.tsx
"use client";
import { useEffect, useState } from "react";
const ClientDataFetcher = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const res = await fetch("https://api.example.com/data");
setData(await res.json());
};
fetchData();
}, []);
return <div>{/* render data */}</div>;
};Next.js recommended approach - fetches data on server, sends complete HTML.
π File: components/dataFetcher.tsx
const DataFetcher = async () => {
const res = await fetch("https://api.example.com/data");
if (!res.ok) throw new Error("Failed to fetch");
const data = await res.json();
return <div>{/* render data */}</div>;
};β Benefits:
| Benefit | Description |
|---|---|
| Better SEO | Content rendered on server |
| Reduced FCP | Faster First Contentful Paint |
| Fewer lines | No useState/useEffect boilerplate |
| Security | API keys stay on server |
| Deduplication | Same requests are automatically deduped |
| No waterfall | Requests can be parallelized |
| HMR Cache | Cached to reduce build-time API calls |
Create route.ts in any folder under /app to create an API endpoint.
π File: app/api/route.ts β GET /api
export async function GET(request: Request) {
return Response.json({ message: "Hello World!" });
}π File: app/api/books/route.ts
// GET /api/books
export async function GET(request: Request) {
return Response.json(books);
}
// POST /api/books
export async function POST(request: Request) {
const book = await request.json();
books.push(book);
return Response.json(book);
}Use [param] folder syntax for dynamic segments.
π File: app/api/books/[id]/route.ts
// PUT /api/books/:id
export async function PUT(
request: Request,
context: { params: { id: string } }
) {
const id = +context.params.id;
const book = await request.json();
// Update logic...
return Response.json(book);
}
// DELETE /api/books/:id
export async function DELETE(
request: Request,
context: { params: { id: string } }
) {
const id = +context.params.id;
// Delete logic...
return Response.json({ success: true });
}π Test endpoints:
GET /api/books- List all booksPOST /api/books- Create bookPUT /api/books/1- Update bookDELETE /api/books/1- Delete book
| Type | What it does |
|---|---|
| Browser Cache | Saves static files locally |
| Server Cache | Stores pre-rendered pages & API responses |
| Data Cache | Remembers fetched data to avoid repeat requests |
| Strategy | Description |
|---|---|
| SSG | Static Site Generation - HTML at build time |
| ISR | Incremental Static Regeneration - updates static pages post-deploy |
| PPR | Partial Pre-Rendering - mix of static + dynamic components |
Mark components, routes, or functions to cache their output.
"use cache";
async function CachedComponent() {
const data = await fetchData(); // Cached!
return <div>{data}</div>;
}Behavior:
- Pre-renders at build time
- Stores in memory
- Auto-revalidates every 15 minutes (default)
Enable in next.config.ts:
const nextConfig = {
cacheComponents: true, // Enable component caching
};Custom cache lifetime:
import { cacheLife, cacheTag } from "next/cache";
async function getData() {
"use cache";
cacheLife("twoweeks"); // How long to cache
cacheTag("products"); // Tag for targeted revalidation
return await fetchProducts();
}Define custom cache profiles in next.config.ts:
const nextConfig = {
cacheLife: {
twoweeks: {
stale: 60 * 5, // Serve stale for 5 min
revalidate: 60 * 60 * 24 * 14, // Revalidate after 2 weeks
expire: 60 * 60 * 24 * 365, // Expire after 1 year
},
},
};Manual revalidation:
import { revalidateTag, revalidatePath } from "next/cache";
// Revalidate by tag
revalidateTag("products");
// Revalidate by path
revalidatePath("/products");| Function | Purpose |
|---|---|
cacheLife() |
Controls when to clear cache |
cacheTag() |
Controls what to clear (targeted) |
revalidateTag() |
Manually refresh tagged cache |
revalidatePath() |
Manually refresh path cache |
Export metadata object for pages with fixed SEO data.
export const metadata = {
title: "Home | My App",
description: "Welcome to my application",
openGraph: {
title: "Home",
images: ["/og-image.png"],
},
};Use generateMetadata for dynamic pages (e.g., blog posts).
export async function generateMetadata({ params }: { params: { id: string } }) {
const post = await getPost(params.id);
return {
title: `${post.title} | My Blog`,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
twitter: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
};
}Place these files in /app for automatic SEO:
| File | Purpose |
|---|---|
robots.txt |
Search engine crawling rules |
sitemap.xml |
Site structure for search engines |
favicon.ico |
Browser tab icon |
icon.png |
App icon |
apple-icon.png |
iOS home screen icon |
opengraph-image.png |
Social sharing preview |
twitter-image.png |
Twitter card image |
robots.txt:
User-Agent: *
Allow: /
Disallow: /private/
Sitemap: https://example.com/sitemap.xml
sitemap.xml:
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com</loc>
<lastmod>2024-01-01</lastmod>
<changefreq>weekly</changefreq>
<priority>1</priority>
</url>
</urlset>π Reference images: Check /public/meta-*.png for icon conventions.
Rust-based bundler for faster development compilation.
# Enabled by default in Next.js 16
npm run devπ Config: next.config.ts
experimental: {
turbopackFileSystemCacheForDev: true, // Extra caching
}Automatically optimizes React code at build time (auto-memoization).
Setup:
npm install babel-plugin-react-compiler@latestπ Config: next.config.ts
const nextConfig = {
reactCompiler: true,
};Adapters let hosting providers customize the build pipeline.
Benefits:
- β Zero-config deployments
- β Environment-specific optimizations
- β Freedom from vendor lock-in
Capabilities:
- Modify Next.js config at build time
- Optimize final build output
- Adjust runtime behavior
| Concept | File Pattern | Example |
|---|---|---|
| Page | page.tsx |
app/about/page.tsx β /about |
| Layout | layout.tsx |
Wraps children routes |
| Loading | loading.tsx |
Suspense fallback |
| Error | error.tsx |
Error boundary |
| Route Group | (folder) |
(dashboard)/ - no URL impact |
| Dynamic Route | [param] |
[id]/page.tsx β /123 |
| API Route | route.ts |
app/api/users/route.ts |
| Catch-all | [...slug] |
app/docs/[...slug]/page.tsx |
| Optional Catch-all | [[...slug]] |
Matches with or without params |
- π¬ Next.js 16 Crash Course (YouTube) - Source tutorial
- Next.js 16 Docs
- React 19 Docs
- Tailwind CSS 4