Introduction π
Next.js ships with a powerful, convention-based routing system built directly on top of your project's folder structure. Instead of manually configuring a router, you simply create folders and files, and Next.js automatically wires them into working routes. This tutorial walks through everything from the basics of file-based routing to advanced patterns like parallel and intercepting routes.
Information
What is File-Based Routing? π
In file-based routing, the structure of your files and folders directly determines the URL structure of your application. There is no separate route configuration file to maintain β the file system is the router.
- A folder defines a URL segment.
- A special file (like page.tsx) inside that folder makes the segment publicly accessible.
- Nesting folders creates nested URL paths.
Tip
App Router π
The App Router lives inside the app/ directory and introduces support for Server Components, layouts, streaming, and colocated route files. It replaced the older pages/ directory (Pages Router) as the recommended architecture.
Note
Route Segments π§©
Each folder in the app directory represents a route segment that maps to a corresponding segment in the URL path. A nested folder structure like app/blog/first-post maps directly to the URL /blog/first-post.
Static Routes π
A static route is the simplest kind of route β a fixed path with no dynamic parameters. You create one by adding a folder with a page.tsx file inside it.
app/about/page.tsx
export default function AboutPage() {
return <h1>About Us</h1>;
}This automatically becomes accessible at /about.
Dynamic Routes π
When a segment's exact value isn't known ahead of time (like a blog slug or user ID), you can use a dynamic segment by wrapping the folder name in square brackets: [param].
app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
return <h1>Post: {params.slug}</h1>;
}Example
Catch-all Routes πΈοΈ
A catch-all route matches an arbitrary number of segments using a spread-like syntax: [...param]. This is ideal for scenarios like documentation sites with deeply nested paths.
app/docs/[...slug]/page.tsx
export default function Docs({ params }: { params: { slug: string[] } }) {
return <p>Path: {params.slug.join("/")}</p>;
}Important
Optional Catch-all Routes β
Adding an extra pair of square brackets β [[...param]] β makes the catch-all segment optional, meaning the base route (with no extra segments) will also match.
| URL | params.slug |
|---|---|
| /docs | undefined |
| /docs/a | ["a"] |
| /docs/a/b | ["a", "b"] |
Nested Routes πͺ
Routes naturally nest by nesting folders. Each level of folder nesting corresponds to a level of URL nesting, and each nested folder can have its own page.tsx, layout.tsx, and other route files.
This maps to /dashboard and /dashboard/settings respectively.
Route Groups ποΈ
Wrapping a folder name in parentheses β (groupName) β creates a route group. Route groups let you organize files logically without affecting the URL path, which is useful for grouping by feature or by layout.
Tip
Private Folders π
Prefixing a folder name with an underscore β _folderName β makes it a private folder, explicitly opting it out of routing. This is useful for colocating implementation details, utilities, or components without them becoming routes.
Note
Parallel Routes π§΅
Prefixing a folder with @ creates a slot, enabling parallel routes β multiple independent pages rendered simultaneously within the same layout. This is powerful for dashboards with distinct, independently-loadable sections.
app/layout.tsx
export default function Layout({
children,
analytics,
team,
}: {
children: React.ReactNode;
analytics: React.ReactNode;
team: React.ReactNode;
}) {
return (
<>
{children}
{analytics}
{team}
</>
);
}Intercepting Routes β
Intercepting routes let you load a route from another part of your application within the current layout β commonly used for modals, like showing a photo in a lightbox while keeping the feed behind it. The convention uses dot-segment prefixes:
| Convention | Meaning |
|---|---|
| (.) | match a segment on the same level |
| (..) | match a segment one level above |
| (..)(..) | match a segment two levels above |
| (...) | match a segment from the root |
Example
Layouts πΌοΈ
A layout.tsx file defines UI that is shared across multiple pages, such as navigation bars or sidebars. Layouts preserve state, remain interactive, and do not re-render on navigation between sibling pages.
app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}Important
Nested Layouts π§±
Layouts can be nested β every folder can define its own layout.tsx, which wraps its own page.tsx as well as any child layouts and pages beneath it, forming a layout hierarchy.
Templates ποΈ
A template.tsx file is similar to a layout in that it wraps child pages, but with one key difference: templates create a new instance on every navigation, resetting state and re-running effects β unlike layouts, which persist.
app/template.tsx
export default function Template({ children }: { children: React.ReactNode }) {
return <div className="fade-in">{children}</div>;
}Tip
Pages π
A page.tsx file makes a route segment publicly accessible and defines the unique UI for that route. Without a page.tsx, a folder is just an organizational segment β it won't be reachable as a URL.
app/contact/page.tsx
export default function ContactPage() {
return <h1>Contact Us</h1>;
}Default Pages π§
A default.tsx file acts as a fallback UI for parallel route slots when Next.js can't recover an active state for that slot on a full page load β for example, when navigating directly to a URL that only affects one slot.
app/@team/default.tsx
export default function Default() {
return null;
}Loading UI β³
Adding a loading.tsx file automatically wraps a page (and its children) in a Suspense boundary, showing instant loading state while content streams in from the server.
app/dashboard/loading.tsx
export default function Loading() {
return <p>Loading dashboardβ¦</p>;
}Best Practice
Error UI π¨
An error.tsx file automatically wraps a route segment in a React Error Boundary. When a rendering error occurs, Next.js shows this fallback UI instead of crashing the whole app.
app/dashboard/error.tsx
"use client";
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<p>Something went wrong!</p>
<button onClick={() => reset()}>Try again</button>
</div>
);
}Important
Global Error UI π
A global-error.tsx file at the root of the app directory catches errors in the root layout itself β something a regular error.tsx cannot do, since it doesn't wrap the layout it lives in.
app/global-error.tsx
"use client";
export default function GlobalError({ error, reset }: { error: Error; reset: () => void }) {
return (
<html>
<body>
<h2>Something went seriously wrong</h2>
<button onClick={() => reset()}>Try again</button>
</body>
</html>
);
}Not Found Pages π
A not-found.tsx file renders when the notFound() function is called, or when a URL doesn't match any route within that segment.
app/blog/[slug]/not-found.tsx
export default function NotFound() {
return <h2>Post not found</h2>;
}Route Handlers π οΈ
A route.tsx (or route.ts) file lets you build API endpoints directly within the app directory, using standard Web Request and Response APIs.
app/api/hello/route.ts
export async function GET() {
return Response.json({ message: "Hello, world!" });
}Caution
Metadata Routes π·οΈ
Next.js supports special files that generate metadata assets, like sitemaps, robots rules, and app icons, without needing to hand-craft static files.
| File | Purpose |
|---|---|
| sitemap.ts | generates sitemap.xml |
| robots.ts | generates robots.txt |
| icon.tsx | generates the app favicon |
| opengraph-image.tsx | generates social share images |
| manifest.ts | generates the web app manifest |
Route Organization π§
Beyond individual conventions, Next.js gives you several tools β route groups, private folders, and colocated components β to keep large projects maintainable without affecting the resulting URL structure.
Navigation Overview π§·
Client-side navigation is handled primarily through the <Link> component and the useRouter hook, both of which enable fast, prefetched transitions between routes without a full page reload.
components/Nav.tsx
import Link from "next/link";
export default function Nav() {
return (
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
</nav>
);
}Tip
Route Configuration βοΈ
Individual route segments can export special variables to configure their runtime behavior, such as caching, revalidation, and rendering strategy.
| Export | Purpose |
|---|---|
| dynamic | control static vs dynamic rendering |
| revalidate | set ISR revalidation interval |
| fetchCache | control fetch request caching |
| runtime | choose Node.js or Edge runtime |
app/blog/page.tsx
export const revalidate = 60;
export const dynamic = "force-static";Route Resolution π§
When a request comes in, Next.js resolves it by matching the URL against the folder structure, prioritizing more specific (static) segments over dynamic ones.
- Static segments are matched first (e.g. /blog/featured).
- Dynamic segments are matched next (e.g. /blog/[slug]).
- Catch-all segments are matched last (e.g. /blog/[...slug]).
Route Matching π―
Understanding matching priority helps avoid ambiguity when multiple segment types could apply to the same URL.
| URL | Matched Segment |
|---|---|
| /blog/featured | blog/featured/page.tsx (static) |
| /blog/hello-world | blog/[slug]/page.tsx (dynamic) |
| /blog/a/b/c | blog/[...slug]/page.tsx (catch-all) |
Route Segment Config π§
Route segment config options let individual pages, layouts, or route handlers opt out of shared behavior. These are declared as simple exported constants at the top of a route file.
| Option | Values |
|---|---|
| dynamic | "auto" | "force-dynamic" | "force-static" | "error" |
| dynamicParams | true | false |
| revalidate | false | 0 | number |
| runtime | "nodejs" | "edge" |
Best Practices β
- Use route groups to organize large apps by feature or team without polluting URLs.
- Keep loading.tsx files lightweight so streaming feels instantaneous.
- Colocate route-specific components inside private folders (_components) to avoid accidental routing.
- Prefer <Link> over manual anchor tags for prefetching and client-side transitions.
- Use parallel routes sparingly β reserve them for genuinely independent UI regions.
Common Mistakes β οΈ
- Forgetting that route groups ((name)) do not appear in the URL, and expecting them to.
- Placing both page.tsx and route.tsx in the same segment, causing a conflict.
- Using [...slug] when [[...slug]] was actually needed for an optional base route.
- Forgetting the "use client" directive on error.tsx files.