Next.js App Router: A Beginner's Guide to the Core Concepts
A clear beginner's introduction to the Next.js App Router — what it is, how file-based routing works, and the mental model you need before writing your first page.
If you've been building with Next.js for a while, you probably started with the Pages Router — a folder of files that mapped cleanly to URLs. The App Router, introduced in Next.js 13 and now the default, looks similar on the surface but operates on a fundamentally different model. If you're new to Next.js entirely, you may have read the documentation and come away with a list of file names but no clear picture of why the system works the way it does.
This guide focuses on the mental model. Get that right, and the file conventions will make sense on their own.
What the App Router Actually Is
The App Router is a React application framework built on top of React Server Components. It handles routing through a file system convention, and it renders pages by mixing server and client components together — with server components as the default.
The key shift from the Pages Router: in the Pages Router, you exported React components from files and Next.js rendered them on the server for you. In the App Router, React Server Components are the runtime, and the routing conventions are the way you organize them.
The app Directory
All App Router code lives in the app directory at the project root. The folder structure defines your URL structure.
app/
page.tsx → /
about/
page.tsx → /about
blog/
page.tsx → /blog
[slug]/
page.tsx → /blog/any-slug-here
Square brackets in folder names create dynamic segments. The value inside the brackets becomes a parameter your page can read.
The Special Files
Five file names have meaning in the App Router. Understanding what each one does is the foundation of everything else.
page.tsx — This is the content for a route. Only files named page.tsx (or page.js) make a route publicly accessible. Other files in the same folder — components, utilities, styles — are colocated but not exposed as routes.
layout.tsx — This is a wrapper that wraps the page and stays mounted as users navigate between routes in the same section. A layout at app/dashboard/layout.tsx wraps every page under /dashboard/*. The root layout at app/layout.tsx wraps everything, including the html and body tags.
loading.tsx — This shows while the page's data is loading. Next.js wraps the page in a Suspense boundary automatically when this file exists. You see the loading state immediately and the real page when data is ready.
error.tsx — This shows when the page throws an error. It replaces the broken page with a recovery UI. You can read the error and offer a retry button.
not-found.tsx — This shows when you call notFound() from within a page, or when no route matches a URL.
Each of these files can exist at any level of the folder hierarchy. Layouts, loading states, and error boundaries nest and apply only to their segment and its children.
Pages Are Server Components by Default
This is the key concept that trips up most beginners. When you write a page.tsx file, it is a React Server Component unless you explicitly mark it otherwise. That means:
- It runs on the server, not in the browser
- It can
awaitdatabase queries or API calls directly in the component body - It never ships its own JavaScript to the client
- It cannot use
useState,useEffect, or any browser API
If you need interactivity — a button that toggles a menu, a form with controlled inputs — you create a separate component file, add 'use client' at the top, and import it into your page. That client component is the only part of the page that ships JavaScript to the browser.
A Simple Page in Practice
Here is a blog post page that fetches its content from a database:
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'
import { getPostBySlug } from '@/lib/db'
export default async function BlogPostPage({
params,
}: {
params: { slug: string }
}) {
const post = await getPostBySlug(params.slug)
if (!post) notFound()
return (
<article>
<h1>{post.title}</h1>
<p>{post.publishedAt}</p>
<div>{post.content}</div>
</article>
)
}This component runs on the server. It queries the database directly. It returns HTML. No client-side JavaScript is involved.
Navigation
Use the Link component from next/link for internal navigation. It handles prefetching automatically and works without JavaScript when needed.
import Link from 'next/link'
export function NavBar() {
return (
<nav>
<Link href="/">Home</Link>
<Link href="/blog">Blog</Link>
</nav>
)
}For programmatic navigation — redirecting after a form submission — use redirect() from next/navigation inside a server component or Server Action, or useRouter() inside a client component.
Route Groups
Sometimes you want shared layouts for a group of routes without affecting the URL. Route groups are folders wrapped in parentheses:
app/
(marketing)/
layout.tsx ← shared marketing layout
page.tsx → /
about/
page.tsx → /about
(dashboard)/
layout.tsx ← shared dashboard layout
settings/
page.tsx → /settings
The (marketing) and (dashboard) folder names do not appear in the URL. They exist purely to organize layouts.
What to Learn Next
Once the mental model clicks, the natural next steps are:
- Data fetching patterns — parallel fetches, Suspense streaming, caching
- Server Actions — how to handle form submissions and mutations without API routes
- Middleware — how to run code before a request reaches a page (authentication, redirects)
- Metadata API — how to set
titleanddescriptionper page for SEO
The App Router has a learning curve, but it is a coherent system. Each feature builds on the same foundation: server components are the default, client components are the exception, and the file system is the router.
If you're a product team looking to build on Next.js and want engineers who know the platform deeply, Clixo is a good starting point. We design and build full-stack Next.js applications for founders and product organizations.