Next.js 15 Tutorial: Build Full Stack Web Apps with the Framework Everyone Uses

In This Guide

  1. Why Next.js Dominates in 2026
  2. Next.js vs. Plain React: When You Need a Framework
  3. App Router vs. Pages Router
  4. React Server Components Explained Plainly
  5. File-Based Routing: layout, page, loading, error
  6. Server Actions: Full-Stack Forms Without API Routes
  7. Data Fetching: Async Components, Streaming, Suspense
  8. Next.js with Databases: Prisma and Drizzle
  9. Authentication: Auth.js Patterns
  10. Deployment: Vercel, Docker, AWS
  11. Next.js for AI Apps: Streaming Claude and OpenAI
  12. What's New in Next.js 15

Key Takeaways

In 2026, if you want to build a serious web application with React, you are almost certainly building it with Next.js. The framework has become the de facto standard for production React apps — not because it is trendy, but because it solves real problems that every developer hits the moment they move beyond a simple client-side app: SEO, performance, server-side rendering, API endpoints, and full-stack data access all live in one cohesive framework.

This tutorial covers Next.js 15 from the ground up. Whether you are coming from plain React, another framework, or starting fresh, this guide will give you a complete map of how Next.js works in 2026, why it is built the way it is, and how to use its most important features — including the parts that are genuinely new and different from what you may have learned before.

6M+
Weekly npm downloads — Next.js is the most-used React framework on the planet
Used by Vercel, OpenAI, Stripe, Nike, TikTok, and thousands of startups

Why Next.js Dominates in 2026

Next.js dominates in 2026 because it appears in 72% of React job postings, collapses routing, SSR, API routes, and deployment into a single framework, and is the only production-grade framework with full React Server Components support — making it the practical destination for any serious React developer, not just an optional add-on.

The short answer: Next.js collapsed what used to require four separate tools — a React app, an Express backend, a bundler configuration, and a deployment pipeline — into a single framework that handles all of it. When you create a Next.js app, you get routing, server-side rendering, static generation, API routes, a production-grade bundler (now Turbopack), image optimization, font optimization, and a direct deployment path to Vercel, all out of the box.

But the more interesting reason Next.js dominates in 2026 is the App Router and React Server Components. These represent a fundamental rethink of how React apps are built — moving computation back to the server by default, eliminating unnecessary client-side JavaScript, and enabling patterns like direct database queries inside components that were simply not possible before.

Vercel, which created and funds Next.js, also provides the best deployment platform for it. But Next.js is fully open source, and you can self-host it anywhere that can run Node.js. The framework's popularity also means an enormous ecosystem of tutorials, libraries, and job postings. In 2026, "Next.js" appears in more React job descriptions than any other qualifier.

72%
of React job postings mention Next.js (2026)
15
Major versions in 7 years — active, well-maintained
1
Framework needed for routing, SSR, API, and deployment

Next.js vs. Plain React: When You Need a Framework

Use Next.js for any public-facing, SEO-dependent, or full-stack application; use plain React with Vite only for fully client-rendered internal tools or dashboards where SEO and server-side rendering are irrelevant — in practice, that means Next.js is the right choice for the vast majority of applications teams build in 2026.

Plain React — a create-react-app or a Vite-based SPA — is a client-side tool. It ships a JavaScript bundle to the browser, which then renders your UI. This works fine for internal tools, dashboards, and apps where SEO does not matter and you control who has access. But it falls apart quickly in production scenarios:

Next.js solves every one of these. Use plain React (with Vite) when you are building a fully client-rendered dashboard or internal tool where these concerns do not apply. Use Next.js for anything public-facing, SEO-dependent, or full-stack. In practice, that is most applications.

Feature Plain React (Vite) Next.js 15
Server-side rendering No Yes (default)
SEO-friendly No Yes
File-based routing No (manual) Yes
API endpoints built-in No Yes
Direct DB access in components No Yes (RSC)
Image / font optimization No Yes
Bundle size Client JS only Server-rendered + minimal client JS

App Router vs. Pages Router

The App Router (stable since Next.js 14) is the correct choice for all new projects in 2026 — it supports React Server Components, Server Actions, nested layouts, and streaming, while the Pages Router (pre-Next.js 13) remains maintained but receives no new features and represents the legacy architecture you will encounter only in older codebases.

If you learned Next.js before version 13 (late 2022), you know the Pages Router: a /pages directory where each file becomes a route, with special functions like getServerSideProps and getStaticProps for data fetching. This system still works and is still maintained, but it is the old way.

The App Router, introduced in Next.js 13 and stable since Next.js 14, is the new architecture. It uses an /app directory and is built entirely around React Server Components. Every new project should use the App Router. Here is how they compare:

Concept Pages Router (old) App Router (new)
Directory /pages /app
Data fetching getServerSideProps, getStaticProps async/await directly in components
Default rendering Client-side Server (RSC)
Layouts Manual (wrap in _app.js) Nested layout.tsx files
Loading states Manual loading.tsx convention
Error handling Manual error.tsx convention

Which Should You Learn?

Learn the App Router. The Pages Router will continue to be supported for years, but all new Next.js development happens in the App Router. Every new tutorial, library integration, and Next.js feature in 2026 targets the App Router first.

React Server Components Explained Plainly

React Server Components run entirely on the server, ship zero JavaScript to the browser, can query databases directly without useEffect or fetch calls, and are the default in the Next.js App Router — add 'use client' only when a component needs onClick handlers, useState, or browser APIs, keeping that directive as far down the component tree as possible.

React Server Components (RSC) are the biggest conceptual shift in React in years, and they are worth understanding clearly before you write a single line of App Router code.

The idea is simple: not all components need to run in the browser. If a component only reads data from a database and renders HTML, running that component in the browser is wasteful — you have to ship the JavaScript, wait for it to load, make a network request, wait for the response, and then render. A Server Component does all of this on the server, sends back finished HTML, and ships zero JavaScript to the client.

In the App Router, all components are Server Components by default. You opt into client-side behavior by adding 'use client' at the top of a file. That one directive tells Next.js: "This component uses browser APIs, event handlers, or React hooks like useState — compile it for the client."

tsx app/users/page.tsx
// This is a Server Component — no 'use client' at the top
// It runs on the server. Zero JavaScript sent to the browser for this component.
import { db } from '@/lib/db'

export default async function UsersPage() {
  // Direct database query — no fetch(), no useEffect, no loading state
  const users = await db.user.findMany()

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}
tsx app/users/like-button.tsx
'use client' // This component uses onClick — it must run in the browser

import { useState } from 'react'

export function LikeButton() {
  const [liked, setLiked] = useState(false)

  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? 'Liked ❤️' : 'Like'}
    </button>
  )
}

The mental model: default to Server Components. Add 'use client' only when a component needs interactivity, browser APIs (window, localStorage), or React hooks. In a well-structured Next.js app, the majority of your components are Server Components — they run faster, ship less code, and are simpler to reason about.

File-Based Routing: layout, page, loading, error

The App Router maps your folder structure directly to URL routes: every folder inside /app is a URL segment, and five special filenames control behavior — page.tsx makes a route accessible, layout.tsx wraps child routes persistently, loading.tsx shows a Suspense fallback while data loads, error.tsx catches thrown errors, and not-found.tsx handles missing routes.

The App Router uses your file and folder structure to define routes. Every folder inside /app is a URL segment. Special filenames have specific roles:

A typical route structure looks like this:

text Directory Structure
app/
  layout.tsx          ← Root layout (html, body, nav, providers)
  page.tsx            ← Homepage ( / )
  loading.tsx         ← Global loading state
  dashboard/
    layout.tsx        ← Dashboard shell (sidebar, header)
    page.tsx          ← /dashboard
    loading.tsx       ← Dashboard loading fallback
    settings/
      page.tsx        ← /dashboard/settings
  blog/
    [slug]/
      page.tsx        ← /blog/any-slug (dynamic route)
      error.tsx       ← Error boundary for blog posts

Dynamic routes use square bracket notation: [slug] captures a variable URL segment. Access it via the params prop passed to page.tsx.

Server Actions: Full-Stack Forms Without API Routes

Server Actions are async functions marked 'use server' that run exclusively on the server and can be passed directly to a form's action attribute — collapsing the previous five-step pattern of form component, onSubmit handler, fetch call, API route, and error handling on both sides into a single server function with zero HTTP configuration required.

Before Server Actions, building a form in Next.js meant: create a form component, wire up onSubmit, write a fetch call to an API route, write the API route handler, handle errors in both places. Server Actions collapse this into a single function.

A Server Action is a function marked with 'use server' that runs exclusively on the server. You can pass it directly to a form's action attribute. When the form submits, Next.js POSTs the form data to the server, runs your function, and returns the result — no API route required, no fetch call, no CORS configuration.

tsx app/contact/page.tsx
import { db } from '@/lib/db'
import { revalidatePath } from 'next/cache'

async function submitContact(formData: FormData) {
  'use server'

  const name = formData.get('name') as string
  const email = formData.get('email') as string

  await db.contact.create({ data: { name, email } })
  revalidatePath('/contact') // Refresh the page cache
}

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

Server Actions work progressively — they function even without JavaScript enabled in the browser. You can also use them with the useActionState hook (Next.js 15) for pending states and validation feedback without writing a separate API layer.

Data Fetching: Async Components, Streaming, Suspense

In the App Router, data fetching requires no useEffect, no useState, and no loading state management — make your Server Component async, await your data directly inside it, wrap slow sections in Suspense boundaries with loading fallbacks, and Next.js streams the page to the browser, delivering the shell instantly while each data section fills in as it resolves.

In the App Router, data fetching is straightforward: make your Server Component async and await your data directly. No useEffect, no loading state management, no useState. The component suspends while the data resolves.

Streaming is where this becomes powerful. When you have multiple slow data sources, you do not have to wait for all of them before showing anything. Wrap each slow section in a <Suspense> boundary with a fallback. Next.js streams the page to the browser — the shell renders immediately, and each suspended section fills in as its data arrives.

tsx app/dashboard/page.tsx
import { Suspense } from 'react'
import { UserStats } from './user-stats'
import { RecentOrders } from './recent-orders'

export default function DashboardPage() {
  return (
    <div>
      <h1>Dashboard</h1>
      // UserStats and RecentOrders load in parallel — neither blocks the other
      <Suspense fallback={<div>Loading stats...</div>}>
        <UserStats />
      </Suspense>
      <Suspense fallback={<div>Loading orders...</div>}>
        <RecentOrders />
      </Suspense>
    </div>
  )
}

// Each of these is an async Server Component
async function UserStats() {
  const stats = await fetchSlowUserStats() // Takes 800ms
  return <StatsCard data={stats} />
}

This pattern — streaming with Suspense — gives you the user experience of a fast page with the simplicity of straightforward async code. No skeleton loaders manually wired up. No global loading state. Just await and Suspense.

Next.js with Databases: Prisma and Drizzle ORM

Use Prisma for larger applications with complex relations and strong migration tooling, or Drizzle ORM for lighter-weight apps and edge deployments — both pair with PostgreSQL via Neon (which offers a serverless free tier), and both let you query the database directly inside Server Components with no API layer required.

Because Server Components and Server Actions run on the server, you can query a database directly from your components — no API needed. The two most popular database ORMs in the Next.js ecosystem are Prisma and Drizzle.

Prisma is the more established choice. You define a schema in a schema.prisma file, run migrations, and get a fully typed PrismaClient. It has excellent tooling, a visual database browser, and works with PostgreSQL, MySQL, SQLite, MongoDB, and more.

Drizzle ORM is newer and gaining significant adoption in 2026. It is lighter weight, uses SQL-like syntax that TypeScript developers find more transparent, and has zero dependencies. It pairs well with serverless environments (Neon, PlanetScale, Turso) because it has no connection pooling overhead.

Recommended Stack for 2026

Authentication: Auth.js Patterns

Auth.js (formerly NextAuth.js v5) is the standard authentication library for Next.js, supporting OAuth providers like Google and GitHub, credentials-based login, magic links, and JWT or database sessions — configured in a single auth.ts file with a Next.js App Router adapter, letting you call auth() directly inside Server Components to get the current session with no context hook or API call needed.

Auth.js (formerly NextAuth.js v5) is the standard authentication library for Next.js. It handles OAuth providers (Google, GitHub, Discord, etc.), credentials-based login, magic links, and JWT or database sessions — all with a Next.js-native adapter that plugs directly into the App Router.

The basic setup involves an auth.ts config file, a route handler at app/api/auth/[...nextauth]/route.ts, and a Session Provider wrapping your app. From there, you call auth() in Server Components to get the current session — no API call, no context hook, just a direct server-side function call.

tsx app/profile/page.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'

export default async function ProfilePage() {
  const session = await auth()

  if (!session) redirect('/login')

  return <h1>Welcome, {session.user?.name}</h1>
}

For protecting routes at the middleware level, Auth.js provides a middleware.ts integration that runs before every request — blocking unauthenticated users before any component renders. This is the right way to protect entire sections of your app (like a /dashboard route group).

Deployment: Vercel, Docker, AWS

Start with Vercel for Next.js deployment — zero configuration, automatic preview deployments per pull request, and a free Hobby tier that handles most personal projects; move to Docker on AWS ECS/Fargate, Fly.io, or the Open Next adapter for Lambda + CloudFront when compliance requirements, cost at scale, or vendor lock-in concerns justify the added operational overhead.

Vercel is the easiest and most powerful deployment target for Next.js. Connect your GitHub repo, push, and you are deployed. Vercel automatically handles edge caching, image optimization, preview deployments for every PR, and serverless function execution for API routes and Server Actions. The free tier is generous for personal projects. Production pricing scales with usage.

Self-hosting with Docker is the right choice if you need more control, want to stay within a cloud provider's billing, or need to deploy inside a VPN or air-gapped environment. Next.js includes a standard Dockerfile in its documentation. Build the image, push to your container registry, and run it on any platform that supports containers: AWS ECS, Google Cloud Run, Fly.io, Render, or a plain VM.

AWS specifically: Most teams on AWS use either ECS Fargate (for always-on containers) or App Runner (for easier auto-scaling). The Open Next adapter (opennextjs.org) allows you to deploy Next.js to AWS Lambda + CloudFront for a fully serverless, pay-per-request architecture that mirrors what Vercel does under the hood.

Start With Vercel

Unless you have a specific reason to self-host, deploy to Vercel. It is the path of least resistance, has zero configuration overhead, and gives you a live URL in under two minutes. You can always migrate to a self-hosted setup later when the business case warrants it.

Next.js for AI Apps: Streaming Claude and OpenAI

Next.js is the dominant framework for AI web applications in 2026 because the Vercel AI SDK's streamText function paired with the useChat hook produces a full streaming chat UI in approximately 30 lines of code, while Server Components and Route Handlers keep API keys on the server — never visible in the browser's network tab, which is the minimum security requirement for any production AI feature.

Next.js has become the dominant framework for AI-powered web applications, and for good reason: streaming AI responses — where tokens appear word by word as the model generates them — maps perfectly to Next.js's streaming architecture.

The Vercel AI SDK (ai on npm) provides a set of hooks and utilities that integrate cleanly with Next.js Route Handlers and Server Actions. On the server, you stream a response using streamText. On the client, you consume it with the useChat hook. The result is a ChatGPT-style streaming interface in about 30 lines of code.

ts app/api/chat/route.ts
import { streamText } from 'ai'
import { anthropic } from '@ai-sdk/anthropic'

export async function POST(req: Request) {
  const { messages } = await req.json()

  const result = streamText({
    model: anthropic('claude-sonnet-4-5'),
    messages,
  })

  return result.toDataStreamResponse()
}
tsx app/chat/page.tsx (Client Component)
'use client'
import { useChat } from 'ai/react'

export default function ChatPage() {
  const { messages, input, handleInputChange, handleSubmit } = useChat()

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}><strong>{m.role}:</strong> {m.content}</div>
      ))}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} />
        <button type="submit">Send</button>
      </form>
    </div>
  )
}

A critical advantage of using Next.js for AI apps: your API keys stay on the server. Never call an AI provider directly from client-side React — your key will be visible in the browser's network tab. Server Actions and Route Handlers keep secrets secret, which is the minimum viable security for any production AI feature.

What's New in Next.js 15

Next.js 15 (released late 2024) delivered three major changes: Turbopack is now stable and production-ready with up to 76% faster dev server startup and 96% faster hot module replacement, fetch() calls are now uncached by default (fixing a major gotcha from Next.js 14), and Partial Prerendering is available experimentally — serving a static CDN-cached shell with dynamic streaming holes on a single page.

Next.js 15, released in late 2024, brought several meaningful improvements that are now the baseline for 2026 development:

Turbopack Stable

Turbopack, the Rust-based bundler that replaces Webpack, is now stable for both development and production builds in Next.js 15. The speed difference is dramatic: local development server startup is up to 76% faster, and hot module replacement is up to 96% faster. For large projects, this is the most immediately noticeable improvement. Enable it by passing --turbopack to your dev command, or simply use Next.js 15 where it is on by default.

Improved Caching Defaults

Next.js 14 had aggressive default caching that surprised many developers — fetch() calls were cached indefinitely by default, causing stale data issues that were hard to debug. Next.js 15 reversed this: fetches are now uncached by default, matching the mental model most developers have. You opt into caching explicitly with { cache: 'force-cache' } or Next.js's revalidate option. This is a breaking change from 14, but a correct one.

Partial Prerendering (Experimental)

Partial Prerendering (PPR) is the most architecturally significant feature in Next.js 15, though it remains experimental. PPR allows a single page to have a statically rendered shell (fast, cached at the CDN edge) with dynamic holes that stream in. In practice, this means your page header, navigation, and static content serve instantly from cache, while your personalized or dynamic content streams in — without splitting your app into separate static and dynamic pages. PPR is the direction Next.js is heading in 2026 and beyond.

useActionState and Form State

Next.js 15 adopted React 19's useActionState hook (previously called useFormState) as the standard pattern for handling Server Action responses with pending states and validation errors. Pair it with useFormStatus for a complete, accessible form pattern without any additional state management libraries.

Upgrading from Next.js 14 to 15

The main breaking change is the caching default inversion. If you have production Next.js 14 apps, audit your fetch() calls and add explicit cache directives where needed. The Next.js codemods (npx @next/codemod@latest upgrade latest) handle most of the migration automatically.

Next.js is at the core of modern AI development.

At Precision AI Academy, you build real applications with the full modern stack — Next.js, server-side data, AI API integration, and deployment. Three days. Small class. Five cities in October 2026.

Reserve Your Seat — $1,490

The bottom line: Next.js is not optional for professional React development in 2026 — it appears in 72% of React job descriptions, and the App Router with Server Components represents the direction React itself is heading. Learn core React first, then commit to the App Router: Server Components default, 'use client' at the leaves, Server Actions for mutations, TypeScript throughout, and Vercel for deployment. That stack covers the full spectrum from marketing sites to AI-powered SaaS.

Frequently Asked Questions

Should I learn Next.js or plain React in 2026?

Learn both, but in order: understand React fundamentals first (components, props, hooks, state), then move to Next.js. The App Router will make much more sense once you understand what React itself is doing. In practice, you will spend 90% of your professional time in Next.js or a similar meta-framework — plain React is now the foundation, not the destination.

Is Next.js too complex for beginners?

The App Router has a steeper initial learning curve than a plain React app, mainly because of the Server Component vs. Client Component mental model. But this complexity pays dividends quickly: once you understand when to use 'use client', the rest of the framework — routing, data fetching, layouts — is more intuitive than what you would build manually. Most developers feel productive in the App Router within a week.

Do I need to use TypeScript with Next.js?

You do not have to, but you should. Next.js has first-class TypeScript support — types for all framework APIs are built in. In 2026, virtually all production Next.js codebases use TypeScript. The tooling is excellent, the error messages are specific, and the IDE support makes you faster. Start a new project with TypeScript from day one.

What is the best way to handle global state in Next.js?

The App Router changes the answer here. In a Server Component-heavy app, much of what you previously put in global state (Redux, Zustand, Jotai) can simply live in the URL (search params) or be fetched directly in Server Components. For genuinely client-side global state — UI state, user preferences, shopping cart — Zustand is the most popular choice in 2026 for its simplicity and small bundle size. Avoid reaching for Redux unless you have a very large team with complex state management needs.

Note: Code examples in this article are illustrative and simplified for clarity. Always consult the official Next.js documentation at nextjs.org for the most current API signatures and configuration options, as the framework evolves rapidly.

Sources: Stack Overflow Developer Survey 2025, GitHub Octoverse, TIOBE Programming Index

BP

Bo Peng

AI Instructor & Founder, Precision AI Academy

Bo has trained 400+ professionals in applied AI across federal agencies and Fortune 500 companies. Former university instructor specializing in practical AI tools for non-programmers. Kaggle competitor and builder of production AI systems. He founded Precision AI Academy to bridge the gap between AI theory and real-world professional application.

Explore More Guides