App Router Basics
The App Router uses a file-system based router where folders define routes and special files define UI and behavior.
Recipe
Quick-reference recipe card — copy-paste ready.
app/
├── layout.tsx # Root layout (required)
├── page.tsx # Home route → /
├── loading.tsx # Loading UI for /
├── error.tsx # Error boundary for /
├── not-found.tsx # 404 UI for /
├── about/
│ └── page.tsx # /about
├── blog/
│ ├── layout.tsx # Nested layout for /blog/*
│ ├── page.tsx # /blog
│ └── [slug]/
│ └── page.tsx # /blog/:slug
└── api/
└── health/
└── route.tsx # GET /api/health
Key rule: A route is only publicly accessible when a folder contains a page.tsx or route.tsx file.
Working Example
// app/layout.tsx — Root Layout (required, wraps every page)
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "My App",
description: "Built with Next.js App Router",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}// app/page.tsx — Home page (Server Component by default)
export default function HomePage() {
return (
<main>
<h1>Welcome</h1>
<p>This is a Server Component — no client JS shipped.</p>
</main>
);
}// app/dashboard/page.tsx — Nested route at /dashboard
export default function DashboardPage() {
return <h1>Dashboard</h1>;
}// app/api/health/route.tsx — Route Handler (API endpoint)
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json({ status: "ok", timestamp: Date.now() });
}When to reach for this: Every Next.js 15+ project uses the App Router. Start here when creating any new route, layout, or API endpoint.
Deep Dive
How It Works
- Folders define route segments. Each folder under
app/maps to a URL segment.app/blog/settings/page.tsxserves/blog/settings. - Special files have reserved meaning. Next.js recognizes
page.tsx,layout.tsx,loading.tsx,error.tsx,not-found.tsx,route.tsx,template.tsx, anddefault.tsx. - Server Components are the default. Files in the App Router are React Server Components unless you add
"use client"at the top. - Colocation is safe. Non-special files (components, utils, styles) placed inside route folders are not exposed as routes.
route.tsxandpage.tsxcannot coexist in the same folder. A route segment is either a page or an API route, not both.- Layouts persist across navigations. They do not remount when a child route changes, preserving state.
loading.tsxcreates an automatic<Suspense>boundary. Next.js wraps the page in Suspense usingloading.tsxas the fallback.error.tsxcreates an automatic Error Boundary. It catches errors in the page and its children but not in the layout at the same level.
Variations
// app/template.tsx — Like layout, but remounts on every navigation
export default function Template({ children }: { children: React.ReactNode }) {
return <div className="animate-fade-in">{children}</div>;
}// app/not-found.tsx — Global 404 page
export default function NotFound() {
return (
<div>
<h2>404 — Page Not Found</h2>
<p>The page you are looking for does not exist.</p>
</div>
);
}// app/api/users/route.tsx — Route handler with multiple methods
import { NextRequest, NextResponse } from "next/server";
export async function GET() {
const users = await db.user.findMany();
return NextResponse.json(users);
}
export async function POST(request: NextRequest) {
const body = await request.json();
const user = await db.user.create({ data: body });
return NextResponse.json(user, { status: 201 });
}TypeScript Notes
// Next.js provides built-in types for page and layout props
// Page props in Next.js 15+ use Promise-based params
interface PageProps {
params: Promise<{ slug: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}
// Layout props always include children
interface LayoutProps {
children: React.ReactNode;
params: Promise<{ slug: string }>;
}Gotchas
layout.tsxdoes not re-render on navigation. If you need fresh state on each navigation, usetemplate.tsxinstead.error.tsxdoes not catch errors in the same-level layout. To catch layout errors, placeerror.tsxin the parent segment.route.tsxmust export named HTTP methods (GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS). Default exports are ignored.- Params are now async in Next.js 15+. You must
await paramsinside pages and layouts — the synchronous API is deprecated. searchParamsare also async in Next.js 15+. Useawait searchParamsoruse(searchParams)— do not destructure synchronously.not-found.tsxin the root catches all unmatched routes. Segment-levelnot-found.tsxonly triggers when you callnotFound().
Alternatives
| Approach | When to Use |
|---|---|
Pages Router (pages/ dir) | Legacy projects not yet migrated |
route.tsx handler | API-only endpoints with no UI |
template.tsx | Need fresh component instance on each navigation |
| Third-party router (TanStack Router) | Non-Next.js React apps needing type-safe routing |
FAQs
What makes a route publicly accessible in the App Router?
A route is only publicly accessible when a folder contains a page.tsx or route.tsx file. Other files (components, utils, styles) colocated in the folder are not exposed as routes.
Can page.tsx and route.tsx coexist in the same folder?
No. A route segment is either a page or an API route, never both. Place your route.tsx in a separate folder (e.g., app/api/health/route.tsx).
Are components in the App Router Server Components or Client Components by default?
Server Components by default. You must add "use client" at the top of a file to make it a Client Component.
What is the difference between layout.tsx and template.tsx?
layout.tsxpersists across navigations and does not remounttemplate.tsxremounts on every navigation, giving fresh state each time- Use
template.tsxfor enter/exit animations or per-navigation logging
Why does error.tsx not catch errors thrown in the same-level layout.tsx?
The error boundary created by error.tsx wraps the page, not the sibling layout. To catch layout errors, place error.tsx in the parent segment.
How does loading.tsx work under the hood?
Next.js automatically wraps the page in <Suspense fallback={<Loading />}>. The loading UI shows instantly while the page streams in.
Gotcha: What happens if you destructure params synchronously in Next.js 15+?
It breaks. Both params and searchParams are now Promise objects in Next.js 15+. You must await params inside pages and layouts. The synchronous API is deprecated.
How do you define an API route handler with multiple HTTP methods?
// app/api/users/route.tsx
import { NextRequest, NextResponse } from "next/server";
export async function GET() {
return NextResponse.json({ users: [] });
}
export async function POST(request: NextRequest) {
const body = await request.json();
return NextResponse.json(body, { status: 201 });
}Where does not-found.tsx trigger automatically vs. manually?
- Root
not-found.tsxcatches all unmatched routes automatically - Segment-level
not-found.tsxonly triggers when you callnotFound()fromnext/navigation
What are the TypeScript types for page and layout props in Next.js 15+?
interface PageProps {
params: Promise<{ slug: string }>;
searchParams: Promise<{
[key: string]: string | string[] | undefined;
}>;
}
interface LayoutProps {
children: React.ReactNode;
params: Promise<{ slug: string }>;
}How do you type a route handler that accepts both GET and POST?
import { NextRequest, NextResponse } from "next/server";
export async function GET(): Promise<NextResponse> {
return NextResponse.json({ status: "ok" });
}
export async function POST(
request: NextRequest
): Promise<NextResponse> {
const body = await request.json();
return NextResponse.json(body, { status: 201 });
}Gotcha: What happens if you use a default export in route.tsx?
It is ignored. route.tsx must export named HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS). Default exports do nothing.
Is it safe to colocate non-route files inside route folders?
Yes. Non-special files (components, utils, styles, tests) placed inside route folders are not exposed as routes. Only files with reserved names like page.tsx and route.tsx are treated as routes.
Related
- Layouts — nested layout composition
- Dynamic Routes — parameterized URL segments
- Loading & Error — loading.tsx and error.tsx patterns
- Route Groups — organizing without affecting URL structure