App Router
App Router fundamentals including file-based routing, layouts, loading states, and parallel routes in Next.js
You are an expert in the Next.js App Router for building modern, server-first React applications.
## Key Points
- Default to Server Components; add `"use client"` only when you need interactivity, browser APIs, or React hooks.
- Keep layouts thin — they persist across navigations so heavy logic in them affects every child page.
- Use `loading.tsx` for route-level skeleton screens and `<Suspense>` for component-level streaming.
- Co-locate components, utils, and tests alongside route files for discoverability.
- Use route groups to share layouts without polluting URL structure.
- Always provide `default.tsx` for parallel route slots to avoid 404s on hard refresh.
- **Forgetting `"use client"`**: Using `useState`, `useEffect`, or event handlers in a Server Component causes a build error. Move interactive code to a Client Component.
- **Over-using Client Components**: Wrapping an entire page in `"use client"` opts out of server rendering for the whole tree. Instead, push client boundaries to the smallest leaf components.
- **Layout vs Template confusion**: Layouts persist state across navigations; templates remount. Use templates when you need fresh state per navigation (e.g., enter animations).
- **Catch-all vs optional catch-all**: `[...slug]` requires at least one segment; `[[...slug]]` also matches the bare path. Mixing them up causes unexpected 404s.
- **Parallel route hard refresh**: Without a `default.tsx`, unmatched parallel slots 404 on full page reload.
## Quick Example
```
app/
products/[id]/page.tsx → /products/123
blog/[...slug]/page.tsx → /blog/2024/my-post (catch-all)
docs/[[...slug]]/page.tsx → /docs or /docs/a/b (optional catch-all)
```
```tsx
// app/dashboard/loading.tsx
export default function Loading() {
return <DashboardSkeleton />;
}
```skilldb get nextjs-skills/App RouterFull skill: 250 linesApp Router — Next.js
You are an expert in the Next.js App Router for building modern, server-first React applications.
Overview
The App Router (introduced in Next.js 13.4+) uses a file-system-based routing model inside the app/ directory. It defaults to React Server Components, supports nested layouts, streaming, and co-location of components, styles, and tests alongside routes.
Core Philosophy
The App Router represents a fundamental shift toward server-first rendering. The core insight is that most UI does not need client-side JavaScript: product listings, blog posts, dashboards, and settings pages can be rendered entirely on the server, sent as HTML, and hydrated only where interactivity is genuinely required. By defaulting to React Server Components, the App Router makes the performant path the easy path.
The file-system routing model is designed around composition, not configuration. Layouts nest automatically, loading and error states are declared as files rather than imperative code, and route groups let you organize without polluting URLs. This means the structure of your app/ directory is both the routing table and the component tree, making the architecture self-documenting and easy to reason about for new contributors.
Streaming and progressive rendering are first-class citizens. Rather than blocking the entire page on the slowest data source, the App Router encourages wrapping independent sections in <Suspense> boundaries so the shell loads instantly and content fills in as it becomes available. This aligns with the principle that perceived performance matters more than absolute performance, and it naturally guides you toward architectures that feel fast.
Core Concepts
File Conventions
Each route segment maps to a folder. Special files define UI and behavior:
| File | Purpose |
|---|---|
page.tsx | Unique UI for a route, makes it publicly accessible |
layout.tsx | Shared UI that wraps child segments |
loading.tsx | Instant loading state (Suspense boundary) |
error.tsx | Error boundary for a segment |
not-found.tsx | 404 UI for a segment |
template.tsx | Like layout but re-mounts on navigation |
default.tsx | Fallback for parallel routes |
Route Groups
Parenthesized folders (groupName) organize routes without affecting the URL:
app/
(marketing)/
about/page.tsx → /about
blog/page.tsx → /blog
(dashboard)/
layout.tsx → shared dashboard chrome
settings/page.tsx → /settings
Dynamic Routes
app/
products/[id]/page.tsx → /products/123
blog/[...slug]/page.tsx → /blog/2024/my-post (catch-all)
docs/[[...slug]]/page.tsx → /docs or /docs/a/b (optional catch-all)
Accessing params in a Server Component:
// app/products/[id]/page.tsx
export default async function ProductPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const product = await getProduct(id);
return <h1>{product.name}</h1>;
}
Layouts and Nesting
Layouts persist across navigations and do not re-render. They receive children:
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex">
<Sidebar />
<main className="flex-1">{children}</main>
</div>
);
}
Parallel Routes
Render multiple pages in the same layout simultaneously using named slots:
// app/dashboard/layout.tsx
export default function Layout({
children,
analytics,
team,
}: {
children: React.ReactNode;
analytics: React.ReactNode;
team: React.ReactNode;
}) {
return (
<div>
{children}
<div className="grid grid-cols-2">
{analytics}
{team}
</div>
</div>
);
}
Slot folders use the @ prefix: app/dashboard/@analytics/page.tsx.
Intercepting Routes
Use (.), (..), (..)(..), or (...) conventions to intercept a route and render it in the current layout (useful for modals):
app/
feed/
page.tsx
(..)photo/[id]/page.tsx → intercepts /photo/[id] when navigating from /feed
photo/[id]/
page.tsx → direct access renders full page
Implementation Patterns
Streaming with Loading States
// app/dashboard/loading.tsx
export default function Loading() {
return <DashboardSkeleton />;
}
For more granular streaming, use <Suspense> directly:
// app/dashboard/page.tsx
import { Suspense } from "react";
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<RecentOrders />
</Suspense>
</div>
);
}
Error Handling
// app/dashboard/error.tsx
"use client";
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>Something went wrong</h2>
<button onClick={() => reset()}>Try again</button>
</div>
);
}
Metadata and SEO
// app/products/[id]/page.tsx
import type { Metadata } from "next";
export async function generateMetadata({
params,
}: {
params: Promise<{ id: string }>;
}): Promise<Metadata> {
const { id } = await params;
const product = await getProduct(id);
return {
title: product.name,
description: product.summary,
openGraph: { images: [product.imageUrl] },
};
}
Static Generation with generateStaticParams
export async function generateStaticParams() {
const products = await getProducts();
return products.map((p) => ({ id: p.id }));
}
Best Practices
- Default to Server Components; add
"use client"only when you need interactivity, browser APIs, or React hooks. - Keep layouts thin — they persist across navigations so heavy logic in them affects every child page.
- Use
loading.tsxfor route-level skeleton screens and<Suspense>for component-level streaming. - Co-locate components, utils, and tests alongside route files for discoverability.
- Use route groups to share layouts without polluting URL structure.
- Always provide
default.tsxfor parallel route slots to avoid 404s on hard refresh.
Common Pitfalls
- Forgetting
"use client": UsinguseState,useEffect, or event handlers in a Server Component causes a build error. Move interactive code to a Client Component. - Over-using Client Components: Wrapping an entire page in
"use client"opts out of server rendering for the whole tree. Instead, push client boundaries to the smallest leaf components. - Layout vs Template confusion: Layouts persist state across navigations; templates remount. Use templates when you need fresh state per navigation (e.g., enter animations).
- Catch-all vs optional catch-all:
[...slug]requires at least one segment;[[...slug]]also matches the bare path. Mixing them up causes unexpected 404s. - Parallel route hard refresh: Without a
default.tsx, unmatched parallel slots 404 on full page reload.
Anti-Patterns
-
Wrapping entire pages in
"use client"to use a single hook. This opts the whole tree out of server rendering, eliminating the performance benefits of React Server Components. Instead, extract the interactive piece into the smallest possible Client Component and keep the rest server-rendered. -
Putting data fetching logic inside layouts. Layouts persist across navigations and do not re-render when child routes change. If your layout fetches user-specific or route-specific data, it will go stale. Fetch in
page.tsxor in dedicated Server Components that are children of the layout. -
Creating deeply nested route groups to "organize" code. Route groups are powerful, but three or four levels of parenthesized folders make the directory tree hard to navigate and the routing behavior hard to predict. Use one level of grouping for logical separation (marketing vs. dashboard) and keep it flat otherwise.
-
Using
template.tsxwhen you actually need a layout. Templates remount on every navigation, which resets all state in their subtree. If you reach for a template because you want a "fresh" page, you probably have a state management issue in a child component. Fix the state rather than forcing a remount. -
Ignoring
loading.tsxand hand-rolling loading states everywhere. The file convention exists specifically to give you automatic Suspense boundaries at the route level with zero boilerplate. Skipping it in favor of manualisLoadingstate in Client Components adds complexity and defeats streaming.
Install this skill directly: skilldb add nextjs-skills
Related Skills
API Routes
Route Handlers for building REST APIs, handling webhooks, streaming responses, and CORS in Next.js App Router
Authentication
Authentication patterns using NextAuth.js (Auth.js) and Clerk for protecting routes and managing sessions in Next.js
Data Fetching
Data fetching and caching strategies including Server Components, fetch options, ISR, and the Next.js cache layers
Deployment
Deployment strategies for Next.js including Vercel, self-hosting with Node.js, Docker containers, and static export
Image Optimization
Image optimization with next/image, asset handling, fonts, and performance tuning for Next.js applications
Middleware
Middleware patterns for request interception, redirects, rewrites, authentication guards, and geo-routing in Next.js