HeoLab
ToolsBlogAboutContact
HeoLab

Free developer tools with AI enhancement. Built for developers who ship.

Tools

  • JSON Formatter
  • JWT Decoder
  • Base64 Encoder
  • Timestamp Converter
  • Regex Tester
  • All Tools →

Resources

  • Blog
  • What is JSON?
  • JWT Deep Dive
  • Base64 Explained

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 HeoLab. All rights reserved.

Tools work in your browser. Zero data retention.

HomeBlogNext.js App Router: The Complete Guide for 2025
Table of Contents▾
  • File System Routing
  • Server Components vs Client Components
  • Layouts and Nested Layouts
  • Server Actions — Forms Without APIs
  • Caching in App Router
  • Streaming with Suspense
  • Route Handlers (API Routes)
  • Metadata API
tutorials#nextjs#react#frontend

Next.js App Router: The Complete Guide for 2025

Master Next.js App Router — Server Components, Server Actions, streaming, caching strategies, layouts, route handlers, and migration from Pages Router.

Trong Ngo
February 25, 2026
5 min read

The Next.js App Router (introduced in Next.js 13, stable in 14) changes how you think about React apps. Server Components, streaming, and Server Actions replace patterns you've used for years. Here's everything you need to know.

Next.js app router file structure and data flow

File System Routing

app/
├── layout.tsx          ← root layout (required)
├── page.tsx            ← / route
├── loading.tsx         ← Suspense boundary for this segment
├── error.tsx           ← error boundary
├── not-found.tsx       ← 404 for this segment
├── blog/
│   ├── layout.tsx      ← shared blog layout
│   ├── page.tsx        ← /blog route
│   └── [slug]/         ← dynamic segment
│       └── page.tsx    ← /blog/:slug route
├── api/
│   └── users/
│       └── route.ts    ← /api/users route handler
└── (marketing)/        ← route group (no URL segment)
    ├── about/page.tsx  ← /about
    └── pricing/page.tsx← /pricing

Server Components vs Client Components

// ✅ Server Component (default in App Router)
// Runs on the server — can use async/await, access DB directly
// Cannot use hooks, browser APIs, or event handlers

import { db } from "@/lib/db"

export default async function PostPage({ params }: { params: { slug: string } }) {
  const post = await db.posts.findUnique({ where: { slug: params.slug } })

  if (!post) notFound()

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

// ✅ Client Component — opt-in with "use client"
// Runs in the browser, can use hooks and events
"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>
  )
}

Rule of thumb: Keep everything Server Components by default. Add "use client" only when you need interactivity or browser APIs.

Layouts and Nested Layouts

// app/layout.tsx — root layout, wraps every page
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Header />
        <main>{children}</main>
        <Footer />
      </body>
    </html>
  )
}

// app/blog/layout.tsx — wraps only /blog/* pages
export default function BlogLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex gap-8">
      <aside><BlogSidebar /></aside>
      <div>{children}</div>
    </div>
  )
}

Server Actions — Forms Without APIs

// app/actions.ts
"use server"

import { revalidatePath } from "next/cache"
import { redirect } from "next/navigation"

export async function createPost(formData: FormData) {
  const title = formData.get("title") as string
  const content = formData.get("content") as string

  // Validate
  if (!title || title.length < 3) {
    return { error: "Title must be at least 3 characters" }
  }

  // Save to DB — direct, no API route needed
  const post = await db.posts.create({ data: { title, content } })

  revalidatePath("/blog")       // invalidate blog cache
  redirect(`/blog/${post.slug}`) // navigate to new post
}

// app/blog/new/page.tsx
import { createPost } from "@/app/actions"

export default function NewPostPage() {
  return (
    <form action={createPost}>  {/* action = Server Action */}
      <input name="title" placeholder="Post title" required />
      <textarea name="content" />
      <button type="submit">Publish</button>
    </form>
  )
}

Caching in App Router

// Full Route Cache: static pages cached at build time
// To opt out (dynamic):
export const dynamic = "force-dynamic"

// fetch() is extended with caching options
const data = await fetch("https://api.example.com/posts", {
  cache: "force-cache",        // static (default)
  next: { revalidate: 3600 },  // ISR: revalidate every hour
})

// Cache tags — invalidate specific data
const posts = await fetch("/api/posts", {
  next: { tags: ["posts"] }
})

// In a Server Action:
import { revalidateTag } from "next/cache"
revalidateTag("posts")  // invalidate all fetches tagged "posts"

Streaming with Suspense

import { Suspense } from "react"

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      {/* Fast components render immediately */}
      <QuickStats />

      {/* Slow components stream in later */}
      <Suspense fallback={<Skeleton />}>
        <SlowAnalyticsChart />
      </Suspense>

      <Suspense fallback={<Skeleton />}>
        <RecentOrders />
      </Suspense>
    </div>
  )
}

// SlowAnalyticsChart is a Server Component with a slow fetch
async function SlowAnalyticsChart() {
  const data = await fetchSlowAnalytics()  // takes 2-3 seconds
  return <Chart data={data} />
}
// The page HTML starts streaming immediately.
// SlowAnalyticsChart's HTML is streamed in when ready.

Route Handlers (API Routes)

// app/api/posts/route.ts
import { NextRequest, NextResponse } from "next/server"

export async function GET(request: NextRequest) {
  const { searchParams } = request.nextUrl
  const page = Number(searchParams.get("page") ?? 1)

  const posts = await db.posts.findMany({
    skip: (page - 1) * 20,
    take: 20,
    orderBy: { createdAt: "desc" },
  })

  return NextResponse.json({ data: posts })
}

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

// app/api/posts/[id]/route.ts
export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  await db.posts.delete({ where: { id: params.id } })
  return new NextResponse(null, { status: 204 })
}

Metadata API

// Static metadata
export const metadata = {
  title: "My Blog",
  description: "A blog about web development",
}

// Dynamic metadata
export async function generateMetadata({ params }: { params: { slug: string } }) {
  const post = await db.posts.findUnique({ where: { slug: params.slug } })
  return {
    title: post?.title ?? "Not Found",
    description: post?.excerpt,
    openGraph: {
      images: [{ url: post?.coverImage }],
    },
  }
}

Try These Tools

JSON Formatter & Validator

Format, validate, and beautify JSON data instantly. Detect errors with precise line numbers.

URL Parser & Builder

Parse any URL into its components and rebuild it. Edit parts individually.

Related Articles

React Performance Optimization: memo, useMemo, useCallback, and Profiler

5 min read

Web Performance Optimization: The 2025 Practical Guide

4 min read

JavaScript Array Methods: The Complete Guide with Examples

5 min read

Back to Blog

Table of Contents

  • File System Routing
  • Server Components vs Client Components
  • Layouts and Nested Layouts
  • Server Actions — Forms Without APIs
  • Caching in App Router
  • Streaming with Suspense
  • Route Handlers (API Routes)
  • Metadata API

Related Articles

React Performance Optimization: memo, useMemo, useCallback, and Profiler

5 min read

Web Performance Optimization: The 2025 Practical Guide

4 min read

JavaScript Array Methods: The Complete Guide with Examples

5 min read