Section 1: Foundations of the App Router
Creating the project
Create a new app with the official starter:
npx create-next-app@latest shelf
Accept TypeScript, ESLint, Tailwind CSS, the src directory and the App Router. Turbopack is the default bundler for development and builds in current versions, so you do not need to opt in. Then start the dev server:
cd shelf
npm run dev
The generated structure
src/app/
layout.tsx root layout with <html> and <body>
page.tsx the home page at /
globals.css
public/ static files served as is
next.config.ts
The root layout is required. It is the only place where you render <html> and <body>, and it is a good place for fonts and global providers. Next.js includes next/font, which downloads fonts at build time and serves them from your own domain, avoiding layout shift and third party requests.
Environment variables
Create a .env.local file for secrets such as the database URL. Variables are available on the server through process.env. Only variables prefixed with NEXT_PUBLIC_ are inlined into client bundles, so never give a secret that prefix. Add .env.local to .gitignore (the starter does this already) and keep a .env.example file with empty values for teammates.
A first route
Create src/app/books/page.tsx:
export default async function BooksPage() {
const books = [{ id: 1, title: "Example" }];
return (
<main>
<h1>My books</h1>
<ul>{books.map((b) => <li key={b.id}>{b.title}</li>)}</ul>
</main>
);
}
Notice the component is async. Server components can await data directly, which is something client components cannot do. Visit /books and view the page source: the list is already in the HTML.
Commit this. Next we look at how routes, layouts and navigation fit together.