Next.js and the App Router
Create a Next.js app and learn how folders become URLs, how layouts wrap pages, and how dynamic routes read their params.
On Day 5 you added React Router to a Vite app. You wrote the list of routes yourself, chose a data library, and you would still need to pick a server, a build setup, and a host. Each of those choices is reasonable, but together they are a lot of setup before you build anything.
A framework makes those choices for you, so they work well together. Next.js is a React framework: you still write React components, and Next.js adds routing, server rendering, data loading on the server, and a production build. The App Router is the part of Next.js that turns the folders inside app/ into the pages of your site.
Create a Next.js app
You need Node.js 20.9 or newer. Check with node -v.
Run the installer
npx create-next-app@latest reading-list-next --yes--yes accepts the recommended defaults: TypeScript, ESLint, Tailwind CSS, no src/ folder, the App Router, Cache Components, and the @/* import alias. If you leave it out, the installer asks you about each option instead.
Start the dev server
cd reading-list-next
npm run devOpen http://localhost:3000. Like Vite, the dev server reloads the page when you save a file.
Look around
The files that matter today are these:
reading-list-next/
app/
layout.tsx the root layout: the <html> and <body> for every page
page.tsx the home page, served at /
globals.css global styles
public/ static files such as images, served as they are
next.config.ts Next.js settings
package.jsonThe scripts in package.json work like the ones in your Vite app: npm run dev for development, npm run build for a production build, and npm start to run that build.
Folders become URLs
In the App Router, file-system routing means the folder structure inside app/ is your list of routes. Each folder is one segment of the URL. A folder only becomes a page you can visit when it contains a page.tsx file.
| File | URL |
|---|---|
app/page.tsx | / |
app/about/page.tsx | /about |
app/books/page.tsx | /books |
app/books/[id]/page.tsx | /books/1, /books/42, /books/anything |
A folder name in square brackets, like [id], is a dynamic segment. It matches any value in that position of the URL, and Next.js hands the value to your page as a param.
Next.js does this matching for you, so you never write it. To make the idea concrete, here is a toy version of the matching. Type different paths into the box and watch which file wins.
Try /, /about, /books, /books/dune, and /books/1/reviews. The last one has no matching file, so it is a 404.
Reading params in a dynamic route
Here is the Reading List detail page. The params prop holds the dynamic segments for this URL.
import { notFound } from 'next/navigation';
import { getBook } from '@/lib/books';
export default async function BookPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const book = await getBook(id);
if (!book) {
notFound();
}
return (
<article>
<h1>{book.title}</h1>
<p>by {book.author}</p>
</article>
);
}Three things here are new:
- The component is
async. That is allowed for Server Components, which you meet properly in the next lesson. paramsis a Promise, so youawaitit before you readid.notFound()fromnext/navigationstops rendering and shows the 404 page. It never returns, so TypeScript knowsbookis defined after theif.
Deep diveA shortcut for typing pages: PageProps
Next.js generates a global helper type called PageProps from your actual routes. You give it the route as a string and it knows which params exist:
export default async function BookPage(props: PageProps<'/books/[id]'>) {
const { id } = await props.params;
// ...
}The types are generated when you run next dev or next build. Writing { params: Promise<{ id: string }> } by hand, as this course does, works the same and makes it clearer what is going on.
Which file renders the URL /books/7?
Layouts wrap pages
A layout is a component that wraps every page below it in the folder tree. It receives the current page as children. The root layout, app/layout.tsx, is required and must render the <html> and <body> tags.
import type { Metadata } from 'next';
import Link from 'next/link';
import './globals.css';
export const metadata: Metadata = {
title: 'My Reading List',
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<header>
<nav>
<Link href="/">Reading List</Link>
</nav>
</header>
<main>{children}</main>
</body>
</html>
);
}The exported metadata object sets the <title> and other tags in the page's <head>, so you do not need a library for it.
The useful part is what happens when you navigate. Next.js swaps the page inside the layout but keeps the layout itself mounted, so anything the layout holds survives the move. Here is the same idea in plain React. Type something into the header's search box, then switch pages.
The search text stays because only the children changed. A layout in a nested folder, like app/books/layout.tsx, wraps only the pages inside app/books/.
Moving between pages with Link
To go to another page, use the Link component from next/link. It renders a normal <a> tag, so it works with the keyboard, middle-click, and "open in new tab".
import Link from 'next/link';
export function BookCard({ book }: { book: Book }) {
return (
<h3>
<Link href={`/books/${book.id}`}>{book.title}</Link>
</h3>
);
}Link adds two things a plain <a> doesn't have:
- Client-side navigation: clicking it swaps the page without a full reload, so layouts and their state survive.
- Prefetching: when a link scrolls into view, Next.js starts loading that page in the background, so the click feels instant.
Use a plain <a> only for links that leave your site.
You navigate from / to /about with a Link. What happens to the root layout?
Add an authors route to the toy router
Go back to the toy router sandbox. Add a file so that /authors/ursula-le-guin matches, and the param is called slug. Then add a real About page to your Next.js project at /about, with a Link to it in the root layout.
Show solution
In the sandbox, add one string to the files array:
const files = [
'app/page.tsx',
'app/about/page.tsx',
'app/books/page.tsx',
'app/books/[id]/page.tsx',
'app/authors/[slug]/page.tsx',
];The param name comes from the folder name, so [slug] gives you { slug: 'ursula-le-guin' }.
In the real project, create the folder and the page:
export default function AboutPage() {
return (
<>
<h1>About</h1>
<p>A small app for tracking the books I want to read.</p>
</>
);
}Then add the link next to the existing one in app/layout.tsx:
<nav>
<Link href="/">Reading List</Link> <Link href="/about">About</Link>
</nav>No route list to update anywhere: the folder is the route.
Day 7 progress
Day 7 overview
Take your Reading List to production with Next.js, Server Components, Server Functions, an accessibility pass, and a real deploy.
Server and Client Components
Learn which components run on the server, which run in the browser, how 'use client' draws the line, and how to stream slow data with Suspense.