Last 30 Days
No notifications
Next.js extends React with file-based routing, server-side rendering (SSR), static site generation (SSG), and API routes — all with zero configuration.
Files in app/ define routes. Every folder is a segment, and special files control behavior:
| File | Purpose |
page.tsx | Route UI (makes the segment publicly accessible) |
layout.tsx | Shared layout wrapping child pages |
loading.tsx | Loading UI (React Suspense boundary) |
error.tsx | Error boundary for the segment |
not-found.tsx | 404 UI |
app/
page.tsx → /
about/page.tsx → /about
blog/[slug]/page.tsx → /blog/:slug| Strategy | When Content Generates | Use Case |
| SSG | At build time | Blog, docs, marketing |
| SSR | On each request | Personalized dashboards |
| ISR | Build + revalidate | Product pages |
| CSR | In the browser | Interactive widgets |
"use client"): Run in the browser, support hooks and interactivity.app/api/hello/route.ts creates an API endpoint at /api/hello:
export async function GET() {
return Response.json({ message: "Hello!" });
}React on its own is just a UI library. To build a real app you need: routing, server-side rendering, file uploads, an API layer, image optimisation, deployment, caching… Next.js bundles all of that around React. It's the most popular React framework, and the reference implementation for Server Components.
If you build with React in 2026, you're very likely doing it inside Next.js (or its competitor Remix).
In the App Router, every folder under /app becomes a URL segment. A page.tsx inside makes it a route.
app/
├── page.tsx → /
├── about/page.tsx → /about
├── blog/
│ ├── page.tsx → /blog
│ └── [slug]/page.tsx → /blog/anything
└── (marketing)/ → grouping folder, not in URL
└── pricing/page.tsx → /pricingSpecial filenames inside any folder:
| File | Purpose |
page.tsx | The actual page UI |
layout.tsx | Wraps every page in this folder + children |
loading.tsx | Shown while the page streams in |
error.tsx | Caught error UI for the segment |
not-found.tsx | 404 for the segment |
route.ts | API endpoint (instead of page) |
// app/about/page.tsx
export default function AboutPage() {
return <h1>About us</h1>;
}That's a real route at /about. No imports, no router config.
// app/layout.tsx — wraps every page in the app
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Navbar />
{children}
<Footer />
</body>
</html>
);
}Nest layouts by putting more layout.tsx files in subfolders. Layouts persist across navigation — a sidebar doesn't unmount when the main area changes.
app/blog/[slug]/page.tsxexport default async function BlogPost({ params }) {
const { slug } = await params; // Next 15+: params is a Promise
const post = await getPost(slug);
return <h1>{post.title}</h1>;
}The folder [slug] matches any string. Use [...slug] for catch-all (/blog/a/b/c).
import Link from "next/link";<Link href="/about">About</Link>
is a beefed-up that does client-side navigation and prefetches the linked page when it scrolls into view.
Every component in /app is a Server Component by default:
// Runs ONLY on the server. Zero JS shipped to the browser.
async function Posts() {
const posts = await db.query(...); // direct DB call!
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}For interactivity (useState, onClick, browser APIs), opt into a Client Component with a directive:
"use client";import { useState } from "react";
export default function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>{n}</button>;
}
Mental model: most pages are Server Components, sprinkled with small interactive Client Component "islands".
awaitIn a Server Component you fetch data inline:
export default async function Users() {
const res = await fetch("https://api.example.com/users");
const users = await res.json();
return <List items={users} />;
}No useEffect, no loading state, no race conditions. The page renders on the server with the data already in place.
1. Adding useState to a server component — error. Mark the file "use client" or split out a small client component.
2. Importing a server-only module into a client component — bundling explosion. Server-only utilities should never reach /client files.
3. Forgetting that params and searchParams are Promises in Next 15+ — await them.
4. Using next/link with inside unnecessarily — modern renders the itself.
5. Storing secrets in code that runs on the client — only NEXT_PUBLIC_* env vars are exposed; everything else stays server-side.
Next.js extends fetch with cache controls:
// Cached indefinitely (build-time, like SSG)
fetch(url, { cache: "force-cache" });// Revalidate every 60 s (ISR)
fetch(url, { next: { revalidate: 60 } });
// Always fresh (SSR-like)
fetch(url, { cache: "no-store" });
// Tag-based invalidation
fetch(url, { next: { tags: ["posts"] } });
// later:
revalidateTag("posts");
The framework decides at build time whether each route is static (built once) or dynamic (built per request) based on these hints.
Drop a loading.tsx next to a page and Next streams it instantly while the page is being prepared:
// app/dashboard/loading.tsx
export default function Loading() {
return <Spinner />;
}Use boundaries inside a page to stream individual slow chunks:
<Suspense fallback={<Skeleton />}>
<SlowChart />
</Suspense>
<Suspense fallback={<Skeleton />}>
<SlowTable />
</Suspense>The user sees the shell + skeletons immediately; chunks fill in as data arrives.
// app/api/users/route.ts
import { NextResponse } from "next/server";export async function GET() {
const users = await db.query(...);
return NextResponse.json(users);
}
export async function POST(req: Request) {
const body = await req.json();
// ...
return NextResponse.json({ ok: true });
}
Each HTTP verb is its own export. Place route.ts anywhere under /app to mount that endpoint.
// app/posts/new/page.tsx
async function createPost(formData: FormData) {
"use server";
const title = formData.get("title");
await db.posts.insert({ title });
revalidatePath("/posts");
}export default function NewPost() {
return (
<form action={createPost}>
<input name="title" />
<button>Save</button>
</form>
);
}
The function runs on the server but you wired it up directly to a form. No API route to maintain. Game-changer for CRUD UIs.
Export a metadata object (or a generateMetadata async function) from any page or layout:
export const metadata = {
title: "My Page",
description: "...",
openGraph: { images: ["/og.png"] },
};Next handles , OG tags, sitemap.xml, robots.txt — out of the box.
middleware.ts at the project root runs before every request. Common uses: auth redirects, geo-based routing, A/B testing, URL rewrites.
import { NextResponse } from "next/server";export function middleware(req) {
const token = req.cookies.get("token");
if (!token && req.nextUrl.pathname.startsWith("/admin")) {
return NextResponse.redirect(new URL("/login", req.url));
}
}
export const config = { matcher: ["/admin/:path*"] };
generateStaticParams for Hybrid SSGTell Next which dynamic params to pre-build at build time:
export async function generateStaticParams() {
const posts = await getAllPostSlugs();
return posts.map((slug) => ({ slug }));
}The matching pages become static HTML at build time; everything else is rendered on demand.
import Image from "next/image";
<Image src="/hero.jpg" width={1200} height={600} alt="Hero" priority />import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
<body className={inter.className}>...
You get automatic resizing, modern formats (AVIF/WebP), lazy loading, and zero-CLS font loading — for free.
@modal/page.tsx) render multiple pages into the same layout simultaneously — useful for dashboards with independent panels.(.)photo/[id]/page.tsx) let you show one route's content inside another's layout (e.g. Instagram-style modal previews while preserving the deep-link URL).export const runtime = "edge"; // or "nodejs" (default)The Edge runtime is a slimmed-down V8 environment running close to your users worldwide. Lower latency, no Node APIs (no fs, no native modules). Great for auth, A/B testing, geo-personalisation.
1. Spin up a Next.js app, build a 3-route site (/, /about, /blog/[slug]) using only Server Components.
2. Add a "Like" button as a small Client Component embedded in the post page.
3. Replace a useEffect + fetch flow with a Server Component that awaits data directly.
4. Build a contact form that posts via a Server Action and revalidates the listing page.