LCM Logo
Web Development

Next.js

A concept-first tour of Next.js — routing, Server vs Client Components, data fetching, and mutations — building on React.

Next.js is the most widely used React framework: it takes React's component model and adds the pieces a real app needs — file-based routing, server rendering, data fetching, and a production build — with sensible defaults. This guide assumes you know the React basics; if not, start with the React guide. Examples target Next.js 16 with the App Router.

Next.js 16 defaults: the App Router (the app/ directory), Turbopack as the bundler, and React 19. The older pages/ router still works but new projects should use app/.

Creating a project

create-next-app scaffolds a project with everything wired up — TypeScript, the App Router, and a dev server.

shell
npx create-next-app@latest my-app
cd my-app
npm run dev   # Turbopack dev server on http://localhost:3000

File-based routing

Routes are folders, not configuration. A folder under app/ becomes a URL segment, and a page.tsx inside it makes that segment publicly routable. A layout.tsx wraps every page beneath it with shared UI (nav, footer) that persists across navigation.

app/layout.tsx
// Wraps all routes; rendered once and kept across navigations
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}
app/about/page.tsx
// Becomes the /about route
export default function About() {
  return <h1>About us</h1>;
}

Server vs Client Components

This is the core mental shift from plain React. In the App Router, components are Server Components by default: they render on the server, can talk to your database or filesystem directly, and ship zero JavaScript to the browser. Add the "use client" directive at the top of a file only when a component needs interactivity — state, effects, or event handlers.

app/page.tsx
// Server Component (default): no "use client", runs on the server
export default function Page() {
  return <p>Rendered on the server.</p>;
}
app/counter.tsx
"use client"; // opt in to the browser: needed for useState, onClick, etc.
import { useState } from "react";

export default function Counter() {
  const [n, setN] = useState(0);
  return <button onClick={() => setN(n + 1)}>{n}</button>;
}

Dynamic routes

A folder named in square brackets captures part of the URL as a parameter. The page receives it via params — which in Next.js 16 is a Promise you must await (a change from older versions).

app/blog/[slug]/page.tsx
export default async function Post({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params; // params is async in Next.js 16
  return <h1>Post: {slug}</h1>;
}

Fetching data

Because Server Components run on the server, data fetching is just async/await inside the component — no useEffect, no loading-state plumbing, no client round-trip. The component renders once the data resolves.

app/penguins/page.tsx
export default async function Penguins() {
  const res = await fetch("https://example.com/penguins.json");
  const penguins: { id: number; species: string }[] = await res.json();

  return (
    <ul>
      {penguins.map((p) => (
        <li key={p.id}>{p.species}</li>
      ))}
    </ul>
  );
}

Mutating data with Server Actions

A Server Action is an async function marked "use server" that runs on the server but can be called from the client — including straight from a form's action. It's the modern way to handle mutations without hand-writing an API route.

app/new/page.tsx
export default function NewPost() {
  async function create(formData: FormData) {
    "use server";
    const title = formData.get("title");
    // ...write to your database here
  }

  return (
    <form action={create}>
      <input name="title" />
      <button type="submit">Create</button>
    </form>
  );
}

Linking and navigation

Use next/link for navigation between routes. It renders an <a> but does client-side transitions and prefetches linked pages in the background, so navigation feels instant.

tsx
import Link from "next/link";

export default function Nav() {
  return <Link href="/about">About</Link>;
}

Metadata

Export a metadata object (or an async generateMetadata function) from a page or layout, and Next.js renders the right <head> tags — titles, descriptions, Open Graph — for SEO and sharing.

app/about/page.tsx
import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "About us",
  description: "Who we are.",
};

export default function About() {
  return <h1>About us</h1>;
}

Next.js 16 changes worth knowing

A few defaults changed recently: params, searchParams, cookies(), and headers() are now async (always await them); the middleware file was renamed from middleware.ts to proxy.ts; Turbopack is the default bundler; and next lint was removed in favor of running ESLint or Biome directly. The npx @next/codemod@canary upgrade latest codemod automates most of these migrations.

On this page