Next.js 15 is the most-used React framework in 2026 by deployment count, and the App Router introduced in Next.js 13 is now the only path for new projects. It is also the framework that confused the most developers in 2024–2025 as the mental model of React Server Components landed. In 2026, that model is clearer, the documentation is better, and the patterns are settled. This is the focused path to productive Next.js.
What changed in 2026
- React 19 is the baseline.
use(), useOptimistic, and the form action API are all stable and deeply integrated with Next.js 15. Server Components and Client Components are no longer experimental.
- Partial Prerendering (PPR) is stable. A single route can have a static shell that renders at build time and async holes that stream in at request time. PPR is the default rendering mode for Next.js 15.
- Turbopack is the default. Turbopack replaced Webpack as the dev bundler in Next.js 15. Cold starts are 5–10× faster than Webpack; full rebuilds are sub-100 ms on most projects.
after() API for deferred work. import { after } from 'next/server' lets you run non-critical side effects (logging, analytics) after the response is sent without blocking the user.
Project setup
npx create-next-app@latest my-app \
--typescript --tailwind --eslint --app --src-dir
cd my-app && npm run dev
The file tree that matters:
src/app/
layout.tsx ← root layout (Server Component)
page.tsx ← / route
blog/
[slug]/
page.tsx ← /blog/:slug
api/
webhooks/
route.ts ← POST /api/webhooks
Server Components vs Client Components
// Server Component (default) — runs on server only
// src/app/posts/page.tsx
import { db } from "@/lib/db";
export default async function PostsPage() {
const posts = await db.post.findMany(); // direct DB call, no API needed
return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>;
}
// Client Component — runs in the browser
// src/components/LikeButton.tsx
"use client";
import { useState } from "react";
export function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>{liked ? "Liked" : "Like"}</button>;
}
The rule: start with Server Components. Add "use client" only when you need browser APIs, event handlers, or useState.
Server Actions for mutations
// src/app/posts/new/page.tsx
import { redirect } from "next/navigation";
import { db } from "@/lib/db";
async function createPost(formData: FormData) {
"use server"; // this function runs on the server
const title = formData.get("title") as string;
await db.post.create({ data: { title } });
redirect("/posts");
}
export default function NewPostPage() {
return (
<form action={createPost}>
<input name="title" placeholder="Post title" />
<button type="submit">Create</button>
</form>
);
}
No API route, no fetch, no state for loading. The form calls a server function directly.
The four caching layers
| Cache |
What it caches |
Invalidation |
| Fetch cache |
fetch() responses |
revalidate option or cache: 'no-store' |
| Full-route cache |
Rendered HTML |
revalidatePath() / redeploy |
| Router cache |
Client-side route segments |
5 min (dynamic) / 30 min (static) |
| Data cache |
Extended fetch semantics |
revalidateTag() |
The most common bug: editing data but forgetting to call revalidatePath('/posts') so the router cache shows stale content.
How to pick your rendering mode
| Use case |
Mode |
How |
| Marketing page |
Static (SSG) |
export const dynamic = 'force-static' |
| Blog post |
Static + ISR |
revalidate = 3600 |
| Dashboard |
Dynamic SSR |
export const dynamic = 'force-dynamic' |
| Mixed (shell + data) |
PPR |
Default in Next.js 15 |
Common mistakes
Importing server-only code in a Client Component. If you import db or fs in a "use client" file, it will fail at runtime. Use import 'server-only' at the top of files that must stay on the server.
Overfetching in Server Components. Just because you can query the DB directly does not mean you should — write lean queries and colocate them with the component that needs the data.
Not handling loading and error boundaries. The loading.tsx and error.tsx files are first-class Next.js conventions; use them instead of manual loading state in every component.
Deploying without environment variable validation. Use @t3-oss/env-nextjs or Zod at startup to validate required env vars. Silent missing vars cause runtime errors in production.
What to skip
- The Pages Router for new projects. It still works but the App Router is the future. New features (PPR,
after(), Server Actions) will not come to the Pages Router.
- Custom webpack config. Turbopack does not support webpack plugins. Audit your existing webpack plugins before migrating; most have Turbopack-native alternatives.
getServerSideProps for anything new. Server Components fetch data directly without this API. getServerSideProps is a Pages Router concept.
FAQ
Next.js vs Remix in 2026?
Remix (now React Router v7) is a strong alternative with a simpler caching model. Next.js has a larger ecosystem and Vercel's infrastructure backing. Both are valid. Next.js wins on ecosystem size; Remix wins on mental model simplicity.
Can I use Next.js with a separate API backend?
Yes. Route Handlers (app/api/*/route.ts) are thin proxies or you skip them entirely. Server Components can call your existing REST or GraphQL API from the server.
How do I handle auth?
Auth.js (formerly NextAuth.js) v5 is built for the App Router and supports Server Actions. It handles sessions, OAuth providers, and database adapters out of the box.
Is Vercel required?
No. Next.js runs on any Node.js host, Docker container, or serverless platform. Vercel is the easiest deployment but not mandatory.
Where to go next
After mastering the App Router, explore how to deploy to Vercel in 2026 to get into production, how to learn Node.js in 2026 to understand the runtime underneath, and how to optimize React performance in 2026 to tune your app under load.