Skip to Content
React in a Week
Day 7 · Production with Next.js

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 dev

Open 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.json

The 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.

FileURL
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.

Live code· A toy file-system router

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.

app/books/[id]/page.tsx
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.
  • params is a Promise, so you await it before you read id.
  • notFound() from next/navigation stops rendering and shows the 404 page. It never returns, so TypeScript knows book is defined after the if.
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:

app/books/[id]/page.tsx
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.

Quick check

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.

app/layout.tsx
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.

Live code· Layouts stay, pages change

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/.

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".

components/BookCard.tsx
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.

Quick check

You navigate from / to /about with a Link. What happens to the root layout?

Try it

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:

app/about/page.tsx
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:

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

0/8

On this page