Next.js App Router Explained: File-Based Routing for Beginners
A clear, beginner-friendly walkthrough of the Next.js App Router. Learn how folders become URLs, how layouts wrap pages, and how dynamic routes, loading states, and error boundaries work in 2026.
The single biggest "aha" moment in Next.js is realising that the entire router is just your folder structure. There is no <Routes> config, no React Router setup, no route table to maintain. You make a folder, drop a page.tsx inside, and that path is now a real URL with server rendering, code splitting, and prefetching already on.
This guide walks through the App Router from the ground up: how folders map to URLs, how layouts wrap pages, what the special files (page, layout, loading, error, not-found) actually do, how dynamic routes capture URL parameters, and how route groups let you organise without affecting URLs. It is the tour every new Next.js developer in 2026 needs.
The Core Rule: Folders Are URLs
Inside the app/ directory:
- A folder name becomes a URL segment.
- A
page.tsx(or.jsx) file makes that folder visit-able. - A folder without a
page.tsxis part of the structure but not a route on its own.
Concrete example:
app/
page.tsx → /
about/
page.tsx → /about
blog/
page.tsx → /blog
[slug]/
page.tsx → /blog/:slug
That is the whole routing system in three lines. Add folders, add URLs.
The Special Files Every App Router Project Uses
The App Router gives a small set of file names superpowers. Each one has to be inside a route folder.
page.tsx— the UI for that route. Required to make the folder a route.layout.tsx— wrapspage.tsxand any nested routes. Required at the root (app/layout.tsx).loading.tsx— shown while the page or its data is loading (a Suspense fallback).error.tsx— catches errors thrown inside the segment (a client component error boundary).not-found.tsx— rendered whennotFound()is called.route.ts— turns the folder into an HTTP API endpoint instead of a page.
You can have all of them in the same folder. Next.js wires them together for you.
Layouts Nest Automatically
A layout.tsx wraps everything inside its folder — including child route segments. That makes shared chrome (sidebars, top navs, footers) trivial.
// app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="grid grid-cols-[16rem_1fr]">
<Sidebar />
<main>{children}</main>
</div>
);
}Now every page under app/dashboard/* automatically renders inside that grid. The root app/layout.tsx wraps everything else and is where your <html> and <body> tags live.
Crucially, layouts persist across navigations. When you click from /dashboard/settings to /dashboard/billing, the dashboard layout does not unmount or refetch — only the inner page swaps. State inside the layout (an open menu, a search input) survives.
Dynamic Routes Use Square Brackets
To capture a part of the URL as a parameter, name the folder with brackets.
app/blog/[slug]/page.tsx → /blog/anything-here
app/shop/[category]/[id]/page.tsx → /shop/shoes/123
app/docs/[...path]/page.tsx → catch-all: /docs/a/b/c
Inside the page, read params from props. In Next.js 15+ they are async:
export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) notFound();
return <article>{post.title}</article>;
}Search-string params work the same way: searchParams: Promise<{ q?: string }>. Always await them — that change in Next.js 15 trips up everyone migrating from older versions.
Route Groups Organise Without Adding URLs
Wrap a folder name in parentheses to group routes without that name showing up in the URL.
app/
(marketing)/
layout.tsx ← marketing-only nav + footer
page.tsx → /
pricing/page.tsx → /pricing
(app)/
layout.tsx ← logged-in chrome
dashboard/page.tsx → /dashboard
(marketing) and (app) are invisible in URLs but each gets its own layout. This is the cleanest way to give your marketing pages and your authenticated app totally different chrome without any conditional rendering inside a single layout.
Loading and Error States, the Easy Way
Drop loading.tsx next to a page.tsx and Next.js automatically wraps the page in a Suspense boundary. While the server component is fetching, users see your loading UI instantly — no spinner code in the page itself.
// app/blog/loading.tsx
export default function Loading() { return <p>Loading posts…</p>; }error.tsx works the same way for runtime errors. It must be a client component ("use client" at top) and gets error and reset props so you can show a friendly message and a "Try again" button.
Linking and Navigation
Use <Link> from next/link for internal navigation. It enables prefetching (the next page is fetched as the link enters the viewport) and client-side transitions (no full reload).
import Link from "next/link";
<Link href="/blog/hello-world">Read the post</Link>For programmatic navigation inside event handlers, use the useRouter hook from next/navigation (note: from next/navigation, not next/router, which is the old pages-router import).
Common Mistakes Beginners Make
- Forgetting
page.tsx. A folder without one is not a route. - Importing
useRouterfromnext/router. Usenext/navigationin App Router code. - Not awaiting
paramsorsearchParamsin Next.js 15+. They are promises now. - Putting
<html>and<body>in a non-root layout. Onlyapp/layout.tsxshould contain them. - Using route groups to share URL prefixes. Use a normal folder for that; route groups are for shared layouts only.
Quick Reference
app/foo/page.tsx→/fooapp/foo/[id]/page.tsx→/foo/:id(asyncparams)app/foo/[...slug]/page.tsx→ catch-allapp/(group)/foo/page.tsx→/foo(group is invisible)layout.tsxwraps the segment + children, persists across navloading.tsx= Suspense fallback.error.tsx= error boundary (client component)not-found.tsxrendered when you callnotFound()fromnext/navigationroute.tsturns the folder into an API endpoint; exportGET,POST, etc.- Navigate with
<Link>(declarative) oruseRouter()fromnext/navigation(programmatic)
Rune AI
Key Insights
- Folders inside
app/are URL segments;page.tsxmakes them routable. layout.tsxwraps a segment and persists across navigations.[param]captures dynamic segments;paramsis async in Next.js 15+ — alwaysawait.loading.tsxanderror.tsxgive you Suspense fallbacks and error boundaries for free.- Route groups
(name)share layouts without showing up in URLs.
Frequently Asked Questions
App Router or Pages Router for new projects?
Are layouts re-rendered on every navigation?
Can a folder have both a `page.tsx` and a `route.ts`?
How do I share state between routes?
Why is my `params` an empty object?
Conclusion
The App Router turns navigation into a filesystem operation. Folders become URLs, special files give those folders behaviour, and layouts let you compose chrome without prop-drilling. Spend an afternoon building a small site with three or four pages, a dynamic blog route, and a shared dashboard layout — the model will lock in fast.