React Framework

Next.js

Next.js is the React framework that adds routing, server rendering, data fetching and a server runtime, so you build a full application instead of assembling one.

intermediate16 min readUpdated Sep 15, 2026
page.tsx
tsx
// app/users/page.tsx
async function getUsers() {
  const res = await fetch("https://api.example.com/users", {
    next: { revalidate: 60 },
  });
  if (!res.ok) throw new Error("Failed to fetch users");
  return res.json();
}

export default async function UsersPage() {
  const users = await getUsers();

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
Built on
React
Router
App Router (app/)
Rendering
Server and client components
Data
Server fetch + server actions
Bundler
Turbopack (webpack fallback)
Deploy
Vercel, Node, Docker, static

Why it matters

Why Next.js leads the React ecosystem

Server rendering by default

Components render on the server first, so pages arrive as HTML and can read data without a client round trip.

Routing from the file system

Folders and files under app/ become routes, layouts and loading states, with no route configuration to maintain.

Optimised out of the box

Image, font, script and bundle optimisations ship with the framework, so performance defaults are good.

The big picture

The three ideas behind Next.js

The file system is the router, components can render on the server, and the server runtime is part of the framework.

The App Router

Routing

A file-system router with nested layouts, loading states, error boundaries and streaming.

Server components

Rendering

Components that run on the server and send HTML, with client components for interactivity.

The server runtime

Backend

Route handlers and server actions let you write backend logic in the same project.

Next.js at a glance

What the App Router gives you

File-based routing

Folders map to URL segments and files define pages, layouts and special states.

Nested layouts

Share UI across routes with layouts that preserve state during navigation.

Server components

Fetch data and render on the server without shipping that code to the browser.

Server actions

Call server functions directly from forms and event handlers, with progressive enhancement.

Route handlers

Build API endpoints as web-standard Request and Response functions.

Built-in optimisation

Images, fonts, scripts and streaming are handled by the framework.

A short history

From a React add-on to a full-stack platform

  1. 2016

    Next.js 1

    Vercel releases a small React framework with server rendering and file-based routing.

    16
  2. 2018

    Next.js 7

    Improvements to build performance and developer experience widen adoption.

    18
  3. 2020

    Next.js 10

    The Image component and analytics make production concerns first-class.

    20
  4. 2022

    Next.js 13

    The App Router and React Server Components reshape how Next.js apps are built.

    22
  5. Today

    A full-stack platform

    Server actions, partial prerendering and Turbopack make Next.js a complete application stack.

    Today

The complete guide

Next.js: Everything you need to know

What is Next.js?

Next.js is a React framework that turns a UI library into a full application platform. It adds file-based routing, server rendering, data fetching, API endpoints, image and font optimisation, and a server runtime. Instead of choosing and wiring a router, a data layer and a build setup, you get a coherent stack designed to work together.

It is maintained by Vercel and has become the default way to build production React applications. The current model is the App Router, built on React Server Components, which changes where your code runs and how data flows.

The App Router and file-based routing

Routes come from the file system. A folder is a URL segment, and a page.tsx file makes it a route.

app/
├── layout.tsx          # root layout, wraps everything
├── page.tsx            # /
├── about/page.tsx      # /about
├── blog/
│   ├── page.tsx        # /blog
│   └── [slug]/page.tsx # /blog/:slug
└── dashboard/
    ├── layout.tsx      # layout for /dashboard/*
    └── page.tsx        # /dashboard

Dynamic segments use square brackets, and layouts nest so shared UI stays mounted during navigation. Special files such as loading.tsx, error.tsx and not-found.tsx handle the corresponding states without extra code.

// app/blog/[slug]/page.tsx
export default async function PostPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const post = await getPost(slug);
  return <article>{post.content}</article>;
}

Server and client components

This is the concept that defines modern Next.js. By default, components are server components: they render on the server, can read data and secrets directly, and add nothing to the browser bundle. Components that need interactivity opt in with "use client".

// app/counter.tsx
"use client";

import { useState } from "react";

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

A server component can import and render a client component, but not the other way around. The rule of thumb: keep components on the server and push "use client" down to the smallest interactive leaf. That keeps the JavaScript you ship to the browser small.

Data fetching

In the App Router you fetch data in server components with the standard fetch function, extended with caching and revalidation options.

// app/posts/page.tsx
async function getPosts() {
  const res = await fetch("https://api.example.com/posts", {
    next: { revalidate: 60 },
  });
  if (!res.ok) throw new Error("Failed to load posts");
  return res.json();
}

export default async function PostsPage() {
  const posts = await getPosts();
  return <PostList posts={posts} />;
}

Because the code runs on the server, there is no loading spinner to wire up and no waterfall on the client. You can revalidate by time, on demand with revalidatePath or revalidateTag, or opt out of caching entirely. Fetch requests are also deduplicated within a render.

Route handlers and server actions

For API endpoints, route handlers export functions that receive a web Request and return a Response.

// app/api/posts/route.ts
export async function GET() {
  const posts = await db.post.findMany();
  return Response.json(posts);
}

export async function POST(request: Request) {
  const body = await request.json();
  const post = await db.post.create({ data: body });
  return Response.json(post, { status: 201 });
}

For mutations from your own UI, server actions are usually simpler. Mark a function with "use server" and call it from a form or an event handler.

// app/posts/new/page.tsx
import { revalidatePath } from "next/cache";

async function createPost(formData: FormData) {
  "use server";
  await db.post.create({ data: { title: String(formData.get("title")) } });
  revalidatePath("/posts");
}

export default function NewPost() {
  return (
    <form action={createPost}>
      <input name="title" required />
      <button type="submit">Create</button>
    </form>
  );
}

Server actions handle validation, database writes and cache revalidation together, and the form works before JavaScript loads. That is progressive enhancement without extra work.

Rendering and caching

Next.js supports several rendering strategies in one app:

  • Static pages are prerendered at build time.
  • Server-rendered pages are generated per request.
  • Streaming sends HTML in chunks so slow data does not block the shell.
  • Client rendering handles interactive islands after hydration.

Caching has several layers, from the fetch cache to the full route cache. Understanding them is the main learning curve, and the defaults are chosen to be fast. When something feels stale, the answer is usually an explicit revalidation call rather than disabling caching everywhere.

Optimisation built in

Next.js ships components that handle common performance work:

  • next/image resizes, lazy-loads and serves modern formats.
  • next/font self-hosts fonts and removes layout shift.
  • next/script controls how third-party scripts load.
  • Turbopack speeds up development and production builds.

These defaults mean a well-built Next.js app is fast without a separate optimisation project.

Best practices

  • Keep components on the server and use "use client" only where needed.
  • Fetch data in server components rather than in effects.
  • Use server actions for mutations and revalidate explicitly.
  • Colocate routes, components and data access under app/.
  • Add loading.tsx and error.tsx for good UX at route boundaries.
  • Prefer the framework’s image and font components over manual handling.
  • Understand caching before turning it off; reach for targeted revalidation.

Common mistakes

  • Sprinkling "use client" at the top of the tree and losing the server benefits.
  • Fetching in useEffect when a server component would do.
  • Forgetting to revalidate after a mutation and showing stale data.
  • Assuming the App Router and Pages Router APIs are interchangeable.
  • Blocking a route on slow data instead of streaming with Suspense.
  • Treating Next.js as only a front-end tool and rebuilding a separate backend.

Where to go next

Next.js is the most complete way to ship React today. Strengthen your React and TypeScript foundations, compare the model with Remix and Astro, and learn the Node.js runtime it deploys to. Then build a small full-stack app with a server component, a route handler and a server action.

Fetching data

In the App Router, fetch data in a server component. There is no loading state to manage and no request from the browser.

Prefer
export default async function Page() {
  const res = await fetch("/api/posts");
  const posts = await res.json();
  return <PostList posts={posts} />;
}
Avoid
"use client";
export default function Page() {
  const [posts, setPosts] = useState([]);
  useEffect(() => {
    fetch("/api/posts")
      .then((r) => r.json())
      .then(setPosts);
  }, []);
  return <PostList posts={posts} />;
}

Mutating data

Server actions run on the server and work without JavaScript, so forms stay simple.

Prefer
async function createPost(formData) {
  "use server";
  await db.post.create({
    title: formData.get("title"),
  });
  revalidatePath("/posts");
}

<form action={createPost}>
  <input name="title" />
</form>
Avoid
async function onSubmit(e) {
  e.preventDefault();
  await fetch("/api/posts", {
    method: "POST",
    body: JSON.stringify(data),
  });
}

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning Next.js?

Our interactive tutorial walks you through Next.js step by step — with quizzes and real code you can run in the browser.