Next.js App Router
Master file-based routing with the App Router for modern Next.js apps.
TL;DR
- 01Create routes by organizing files in the app folder structure.
- 02Each page.tsx or page.jsx file becomes a route automatically.
- 03Use brackets [param] for dynamic routes like /blog/[slug].
Tips
- 01Use route groups (parentheses) to organize routes without affecting URLs — great for separating marketing and app sections.
Warnings
- 01Avoid deeply nested folder structures — keep routes 3–4 levels deep for clarity and maintainability.
Basic Routing
- Create pages by adding folders and page.tsx files.
app/ page.tsx # / route about/page.tsx # /about route blog/page.tsx # /blog route - Each folder represents a path segment in the URL.
- Rename page.tsx files to match your naming convention.
- Export a default React component from each page file.
// app/about/page.tsx export default function About() { return <h1>About Us</h1>; } - Use async functions in pages to fetch data on the server.
// app/blog/page.tsx export default async function Blog() { const posts = await getPosts(); return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>; }
Params and Static Generation
- Create dynamic routes with bracket syntax [param].
app/ blog/ [slug]/ page.tsx # /blog/:slug route - Access dynamic parameters — params is a Promise in Next.js 15, must be awaited.
export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; return <h1>Post: {slug}</h1>; } - Use multiple dynamic segments for nested routes.
app/ users/[id]/posts/[postId]/page.tsx # /users/:id/posts/:postId - Generate static pages from dynamic params with generateStaticParams.
export async function generateStaticParams() { const posts = await getPosts(); return posts.map(p => ({ slug: p.slug })); } - Use notFound() to return a 404 when a resource is missing.
import { notFound } from "next/navigation"; export default async function Post({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; const post = await getPost(slug); if (!post) notFound(); return <article>{post.content}</article>; }
Layouts
- Create shared layouts with layout.tsx files.
// app/layout.tsx export default function RootLayout({ children }) { return ( <html> <body> <Header /> {children} <Footer /> </body> </html> ); } - Each folder can have its own layout.
// app/blog/layout.tsx export default function BlogLayout({ children }) { return ( <div> <Sidebar /> {children} </div> ); } - Nested layouts wrap their children.
- Root layout must include html and body tags.
// app/layout.tsx — required at the root export default function RootLayout({ children }) { return ( <html lang="en"> <body>{children}</body> </html> ); } - Export metadata from a layout to share across child pages.
export const metadata = { title: "My Blog", description: "Posts about web development" };
Error and Loading Files
- Use error.tsx for error boundaries — it must be a Client Component.
// app/blog/error.tsx "use client"; export default function Error({ error, reset }) { return ( <div> <h2>Error loading blog</h2> <button onClick={() => reset()}>Try again</button> </div> ); } - Use loading.tsx for Suspense fallbacks.
// app/blog/loading.tsx export default function Loading() { return <p>Loading posts...</p>; } - Use not-found.tsx for 404 pages.
// app/blog/[slug]/not-found.tsx export default function NotFound() { return <h1>Post not found</h1>; } - Use template.tsx for re-mounting a layout on navigation.
// app/blog/template.tsx export default function Template({ children }) { return <div>{children}</div>; // Remounts on each navigation } - Use route.ts files to define API handlers inside the app router.
// app/api/hello/route.ts export async function GET() { return Response.json({ message: "Hello" }); }
Advanced Patterns
- Group routes with parentheses (don't affect URL).
app/ (marketing)/ page.tsx # / (still at root) about/page.tsx # /about (dashboard)/ layout.tsx page.tsx # /dashboard - Create catch-all routes with [...slug].
app/ docs/[...slug]/page.tsx # /docs/a/b/c - Use optional catch-all [[...slug]].
app/ docs/[[...slug]]/page.tsx # Matches /docs, /docs/a, /docs/a/b/c - Use parallel routes with @ to render multiple pages simultaneously.
app/ @analytics/ page.tsx @team/ page.tsx layout.tsx # Receives both slots as props - Use intercepting routes to show modals without full navigation.
app/ photos/ [id]/ page.tsx # /photos/42 — full page (.)photos/ [id]/ page.tsx # intercepted modal view
FAQ
Use [...slug] folder syntax to match multiple path segments — for example, app/docs/[...slug]/page.tsx matches /docs/a, /docs/a/b, and so on. In Next.js 15, params is a Promise: type it as Promise<{ slug: string[] }> and await it before accessing the slug array.
layout.tsx persists across route changes and does not remount, making it ideal for shared UI like navbars. template.tsx creates a new instance on every navigation, which is useful when you need effects or animations to re-run between pages.
Wrap the routes in a route group by naming the folder with parentheses, like (marketing) or (app). Files inside are grouped under that layout but the folder name is excluded from the URL.
Yes — nest dynamic folders like app/shop/[category]/[productId]/page.tsx to capture multiple params. In Next.js 15, type params as Promise<{ category: string; productId: string }> and use const { category, productId } = await params before accessing the values.
loading.tsx only triggers on the initial load of a segment, not on client-side navigations within the same layout boundary. Wrap slow data-fetching components in individual Suspense boundaries to get streaming loading states on every navigation.