Skip to main content
Roadmap progress
intermediatePage 32 of 36

Stage 8 — Next.js

A framework built on React — routing, server components, server actions, image optimisation, and a deploy pipeline. The default React stack for 2026.

Stage 8 — Next.js

Time budget: ~3–4 weeks

In one line: React, plus routing, server rendering, image and font optimisation, API endpoints, and a production-ready build/deploy pipeline.

Next.js is a framework built on React. Where React is just the UI library, Next.js bundles in routing, server-side rendering, image and font optimisation, API endpoints, and a production-ready build/deploy pipeline. It's the default React stack for 2026 — Vercel (the company that makes it) and the React core team are deeply aligned on its direction.

For where Next.js sits among other React meta-frameworks, see Frontend Frameworks. For the rendering strategies it uses under the hood, see SSR, SSG, and ISR / Streaming / PPR.

1. Create your app

npx create-next-app@latest my-site
# answer the prompts: TypeScript yes, Tailwind yes, App Router yes, ESLint yes, src/ no, alias yes
cd my-site
npm run dev

Open http://localhost:3000. You're running a Next.js app. The first thing to notice: a file in app/page.tsx became the page at /. That's file-based routing.

2. File-based routing (the App Router)

app/
├── page.tsx # → /
├── about/
│ └── page.tsx # → /about
├── blog/
│ ├── page.tsx # → /blog
│ └── [slug]/
│ └── page.tsx # → /blog/anything ([slug] is a parameter)
└── layout.tsx # wraps every page in the app

Folders are URL segments. page.tsx is the actual page component. Square-brackets denote dynamic segments. layout.tsx wraps every child page — perfect for nav and footer.

3. Server vs Client Components — the headline feature

This is what makes Next.js (and modern React) different from anything before. Every component is a server component by default. Server components run on the server only — they can be async, talk directly to a database, read files, hold secrets — and ship zero JavaScript to the browser. They produce HTML.

// app/blog/page.tsx — server component (the default)
async function BlogIndex() {
// fetch directly from your database; no useEffect, no loading state
const posts = await db.select().from(postsTable);
return (
<ul>
{posts.map(p => <li key={p.id}>{p.title}</li>)}
</ul>
);
}
export default BlogIndex;

When you need interactivity — state, effects, event handlers — you mark a component as client with "use client" at the top of the file:

// app/components/LikeButton.tsx
"use client";
import { useState } from "react";

export function LikeButton() {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(true)}>{liked ? "♥" : "♡"}</button>;
}

The pattern: server components for everything that just shows data; client components for the small islands that need interactivity. Most of your tree is server; client is the exception.

Every request: route match → server components run on the server (no JS shipped for them) → HTML streams to the browser → small client-component islands hydrate on top.

4. Server Actions: handling form submissions without API routes

// app/contact/actions.ts
"use server";
import { db } from "@/lib/db";

export async function sendMessage(formData: FormData) {
const email = formData.get("email") as string;
const body = formData.get("body") as string;
await db.insert(messages).values({ email, body });
}
// app/contact/page.tsx
import { sendMessage } from "./actions";

export default function ContactPage() {
return (
<form action={sendMessage}>
<input name="email" type="email" required />
<textarea name="body" required />
<button type="submit">Send</button>
</form>
);
}

No fetch call, no API route, no JSON serialisation by you. Next.js secretly turns the action call into an HTTP POST. This is where Next.js feels like a different kind of framework.

import Link from "next/link";

<Link href="/about">About</Link>

Use Link instead of <a> for internal navigation. It prefetches the destination, transitions without a full page reload, and feels instant.

6. Image and font optimisation

import Image from "next/image";
import { Inter } from "next/font/google";

const inter = Inter({ subsets: ["latin"] });

// in your layout:
<body className={inter.className}> ... </body>

// in a page:
<Image src="/me.jpg" alt="Tony" width={400} height={400} />

next/image automatically resizes, compresses, and serves modern formats (WebP/AVIF). next/font loads Google Fonts at build time with zero layout shift.

7. Deploying to Vercel

Push your repo to GitHub. Go to vercel.com, click "Import Project," select your repo. The defaults are correct. Click Deploy. You're live on a your-app.vercel.app URL within a minute. Every push to main after that auto-deploys; every push to a branch gets its own preview URL. This is genuinely free for personal projects.

Where to go deeper

  • Next.js Learn — the official interactive tutorial. Builds a dashboard app step by step. The best single resource for this stage.
  • Next.js docs — reference. The "App Router" sections are the only ones that matter for new projects.
  • Lee Robinson on YouTube — VP of Product at Vercel. Tutorial videos that match Next.js's current direction.

Deeper in this guide

  • Frontend Frameworks — Next.js vs Remix vs SvelteKit vs the rest.
  • SSR — server-side rendering, the strategy Next.js extends.
  • SSG — static generation, the other half of Next.js's hybrid model.
  • ISR / Streaming / PPR — the newer rendering modes Next.js is built around.

Project

Project — A multi-page Next.js site

Use npx create-next-app@latest. Build a site with at least: a home page (hero + 3 feature cards), an "about" page, a "blog" section that lists posts from a hardcoded array, and a dynamic /blog/[slug] page that renders one post. Use the Link component for internal navigation. Use next/image for at least one image. Use a server action for a contact form. Deploy it to Vercel and share the link. You now have a real, public, full-stack site.

Common mistakes

Where people commonly trip up
  • Sprinkling "use client" at the top of every file "just to be safe." That defeats the entire point — you ship JS for everything, lose the ability to await your DB directly, and the bundle balloons. Default to server; opt in to client only for the leaves that need state or event handlers.
  • Calling your DB from a client component. Server-only modules (database clients, secret-holding SDKs) imported into a client component leak secrets and break the build. The fix is to keep data access in server components or server actions, and pass the result down as props.
  • Using <a href> for internal navigation. A plain anchor triggers a full page reload — you lose React state, refetch everything, and feel sluggish. Use <Link> for any same-site URL; reserve <a> for external links.
  • Confusing server actions with API routes. Server actions are a built-in shortcut for "this form submit runs this server function." You don't need to write a separate route, fetch it from the client, or JSON-stringify the body — Next.js handles all of that.

Page checkpoint

Checkpoint Quiz

Did Stage 8 stick?

Required

Next: Stage 9 — Ship a real portfolio · Back to Part I overview