What you'll learn
By the end of this you'll understand how the App Router works: folders become URLs, most of your code runs on the server, and you only reach for "use client" when something actually needs the browser. You'll also have a working project structure you won't regret at 11pm when something breaks.
This isn't an exhaustive feature dump. It's the minimum you need to ship something real and not do it wrong.
Who this is for
- You know React components and props but haven't touched Next.js yet
- You've used the old Pages Router and want to understand what actually changed
- You're building a blog, a docs site, or any content site that needs decent SEO
You can skip this if you're already shipping App Router projects and your actual problem is edge caching or advanced ISR. We cover caching in a later guide — we'll link it when it's live.
What is the App Router?
The App Router is Next.js's current routing system. Folders inside app/ define your URLs. That's really the whole idea.
app/page.tsx→/app/about/page.tsx→/aboutapp/developers/nextjs/page.tsx→/developers/nextjs
It's also server-first. Files in app/ are Server Components by default, which means HTML is built on the server before it reaches the browser. For SEO that matters a lot — Googlebot doesn't have to wait for JavaScript to hydrate before it can read your content.
Plain English version: folders become URLs, and the server does the heavy lifting.
Prerequisites
You'll need:
- A basic grip on JavaScript and React (components, props, JSX)
- Node.js 20 or newer
- A terminal and any code editor (VS Code and Cursor both work fine)
Check your Node version:
node -v
npm -v
If that prints something ancient, grab the latest LTS from nodejs.org and reopen your terminal.
Setup from zero
Step 1 — Scaffold the project
Run this in whatever folder you keep your side projects:
npx create-next-app@latest my-app
When it asks questions, the answers that matter for this tutorial:
- TypeScript → Yes
- ESLint → Yes
- Tailwind CSS → your choice (yes if you want utility classes)
src/directory → Yes- App Router → Yes (this whole guide assumes it)
- Import alias
@/*→ Yes
Step 2 — Start dev
cd my-app
npm run dev
Go to http://localhost:3000. You should see the starter page. If the port was occupied, Next.js will tell you which one it used instead.
Step 3 — Know the important folders
Here's what a tidy project looks like after you clean up the starter noise:
src/
app/
layout.tsx
page.tsx
globals.css
components/
modules/
Little tip: routes go in app/, reusable UI goes in components/, and data/business logic goes in modules/ (or lib/ if you prefer). Don't dump everything into app/ just because it technically works.
Core idea / mental model
Three layers. Seriously, just three:
- Layout — the chrome that wraps many pages: header, footer, nav
- Page — the unique content for one URL
- Components — building blocks you reuse
Server Components are the default. They render HTML on the server and ship it to the browser. No client-side JavaScript unless you explicitly ask for it.
When you need browser features — useState, onClick, localStorage — you add "use client" at the top of that file. Not the whole page. Just the component that needs the browser.
Mental model: server by default, client only when you actually need the browser.
And if you internalize just that one sentence, the rest of the App Router starts making sense pretty quickly.
Key terms
- App Router — folder-based routing under
app/ - Server Component — renders on the server; ships no client JS by default
- Client Component — runs in the browser; marked with
"use client" - Layout — shared wrapper that persists across child routes
- Metadata — title/description for SEO and social cards
- Route segment — one folder level in your URL path
- Dynamic segment — a folder like
[slug]that matches any value
Step-by-step: building real routes
Step 1 — Fix the home page
Open src/app/page.tsx and replace the starter:
export default function HomePage() {
return (
<main>
<h1>Baseline Notes</h1>
<p>Practical Next.js guides for developers who want to ship things.</p>
</main>
);
}
No "use client". No hooks. Just a function that returns JSX.
Step 2 — Add an About page
Create the folder src/app/about/ and put page.tsx in it:
export default function AboutPage() {
return (
<main>
<h1>About</h1>
<p>We write Next.js tutorials that don't make you want to close the tab.</p>
</main>
);
}
Visit /about. The folder name became the URL. That's the whole routing system, mostly.
Step 3 — Nested route
Create src/app/developers/nextjs/page.tsx:
export default function NextjsHubPage() {
return (
<main>
<h1>Next.js notes</h1>
<p>Two folders deep. Two URL segments. It just works.</p>
</main>
);
}
Visit /developers/nextjs.
Step 4 — Root layout (the shared chrome)
Edit src/app/layout.tsx:
import type { ReactNode } from "react";
import "./globals.css";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<header>
<a href="/">Home</a>
{" · "}
<a href="/about">About</a>
</header>
{children}
<footer>© Baseline Notes</footer>
</body>
</html>
);
}
The header and footer stay on every page. Only {children} changes.
Little tip: swap raw <a> tags for Next.js Link for internal navigation — it prefetches and feels snappier:
import Link from "next/link";
<Link href="/about">About</Link>
Working examples
Example A — dynamic blog route
Create src/app/blog/[slug]/page.tsx:
type Props = { params: Promise<{ slug: string }> };
export default async function BlogPostPage({ params }: Props) {
const { slug } = await params;
return (
<main>
<h1>Post: {slug}</h1>
<p>One file. Every URL. Try visiting /blog/hello-world.</p>
</main>
);
}
The square brackets mean "this segment can be anything." One file serves every blog post URL.
Example B — async Server Component with data
async function getPosts() {
return [
{ slug: "app-router", title: "App Router basics" },
{ slug: "images-fonts", title: "Images and fonts" },
];
}
export default async function BlogIndexPage() {
const posts = await getPosts();
return (
<main>
<h1>Blog</h1>
<ul>
{posts.map((post) => (
<li key={post.slug}>{post.title}</li>
))}
</ul>
</main>
);
}
The page is async. That's expected and fine for Server Components. In a real project, swap getPosts for a database call or module import.
Example C — Client Component island
This is the pattern worth internalizing. Keep the page server-side, extract only the interactive piece:
src/components/LikeButton.tsx:
"use client";
import { useState } from "react";
export function LikeButton() {
const [likes, setLikes] = useState(0);
return (
<button type="button" onClick={() => setLikes((n) => n + 1)}>
Likes: {likes}
</button>
);
}
Use it inside a server page:
import { LikeButton } from "@/components/LikeButton";
export default function PostPage() {
return (
<main>
<h1>My post</h1>
<LikeButton />
</main>
);
}
The server renders everything. The browser only hydrates the tiny button. Don't fight this pattern — it's the whole point.
Metadata for SEO
Export a metadata object from any page or layout:
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Next.js App Router basics — Baseline Notes",
description: "Learn App Router routing, layouts, and Server Components from zero.",
};
For dynamic titles (like blog posts that pull from a database), you'd use generateMetadata instead. That's a whole separate guide coming soon.
Patterns and options to know
A few common patterns that come up early:
- Thin route file + separate data module —
page.tsxcalls a function frommodules/content/; keeps routes clean - Nested layout — create a
layout.tsxinside a sub-folder for section-specific chrome (a sidebar, a sub-nav) - Loading state — create
loading.tsxnext topage.tsx; Next.js shows it while the async page resolves - Not found — create
not-found.tsxto customize the 404 experience per route segment
When to use "use client": forms with local state, toggle components, anything that reads from window or localStorage. Not for entire pages.
Recommended project structure
src/
app/ # routes only, thin files
layout.tsx
page.tsx
blog/
[slug]/
page.tsx
developers/
nextjs/
page.tsx
components/ # shared UI
modules/
content/ # posts, types, data helpers
seo/ # metadata, JSON-LD
Routes stay thin. Logic stays testable. You won't confuse "URL files" with "data code."
Common mistakes
- Putting
"use client"on the whole page because only a button needs it - Fetching SEO-critical data in the browser instead of the server
- Mixing Pages Router (
pages/) patterns into App Router projects without thinking - Forgetting
lang="en"on<html>(accessibility and SEO both care, takes three seconds to fix) - Using raw
<a href>for every internal link whenLinkis right there - Only running
npm run buildright before a deploy — production catches thingsdevhides
Troubleshooting
- 404 on a new page — folder might have a typo, or the file is
Page.tsxnotpage.tsx(case-sensitive on Linux/Vercel) useStateerror / onClick does nothing — missing"use client"at the top of that component- Styles completely missing —
globals.cssneeds to be imported inlayout.tsx - Port 3000 occupied — Next.js prints the alternative port; or kill the old process
@/import not resolving — checkpathsconfig intsconfig.json
Checklist before you ship
- [ ] Project runs with
npm run devwithout errors - [ ] Home + at least one nested route both render correctly
- [ ] Root layout has
lang="en"and working navigation - [ ] Pages are Server Components by default (no
"use client"unless actually needed) - [ ] Client JS only on interactive islands, not whole pages
- [ ] Basic
metadataset at minimum on root layout - [ ] Internal navigation uses
Linknot raw<a> - [ ]
npm run buildcompletes without errors
Practice task
Build this in around 20–30 minutes:
- Home page with a short intro
/aboutpage with a sentence or two/bloglist page — 3 hardcoded posts/blog/[slug]page that shows the slug in theh1- Root layout with nav links to
/,/about, and/blog
If that works end-to-end, you've got the App Router fundamentals down.
FAQ
Do I still need the Pages Router?
Not for new projects. It still works in existing codebases, but App Router is the current approach and where all Next.js development is going.
Isn't Server Components complicated?
The rule isn't that bad: no hooks, no event handlers in Server Components. When you need those things, push them into a small "use client" child component.
Can I use React Query or SWR?
Yes, inside Client Components. For public blog-type pages where data doesn't change per user, server rendering is usually cleaner and better for SEO anyway.
Is the src/ directory required?
Nope. app/ at the root works fine. src/ is just a tidiness preference.
What about API routes?
Route Handlers in app/api/.../route.ts. That's a separate guide.
What to learn next
- Next.js images and fonts — the companion Phase 1 post; covers
next/image, LCP, andnext/font - Metadata and SEO —
generateMetadata, JSON-LD, Open Graph - Caching and revalidate — when and how Next.js updates content
- Route Handlers — simple APIs without a separate server
Related on Baseline
- Next.js images and fonts — the other Phase 1 guide, good follow-up read
- Next.js metadata and SEO — coming soon
- Next.js caching and revalidate — coming soon
- Baseline tools: JSON Formatter, JWT Decoder — free, no login
- Developers hub — React, MERN, Node.js notes
Takeaways
- Folders in
app/become URLs. That's the routing system. - Server Components are the default — great for SEO, ships less JS
- Layouts share persistent chrome; pages are the unique parts
"use client"is for interactive islands, not entire pages- Keep routes thin; put logic in modules
If you remember only one thing: server by default, client only when you need the browser.