FR · Français

A hands-on course for new frontend developers

Next.js from Zero to a Real Company Website

You know some HTML, CSS, and JavaScript. You have 14 days. You spend the first 6 learning Next.js with this course. The rest of the time, you build and deliver the website of a real company, which your trainer will hand you.

6 days to learn 12 practice exercises 1 real company website to deliver No prior React needed

The single most important idea in this course

app/page.jsbecomesyoursite.com/
app/about/page.jsbecomesyoursite.com/about
app/services/page.jsbecomesyoursite.com/services

In Next.js, folders are your URLs. If you understand this one sentence, everything else in this course is detail.

Green = files & server things Blue = URLs in the browser Amber = interactive (client) things
Course contents

How to use this course

  • Type every code example yourself. Do not copy-paste. Typing is how the syntax enters your fingers.
  • Do every exercise before opening the solution. Being stuck for 10 minutes teaches more than reading for an hour.
  • The checkboxes save automatically in your browser, so you can close this page and continue later.
  • Follow the schedule below. One day at a time. One day late is fine; three days late, tell your trainer.
  • Stuck for more than 15 minutes? Every chapter ends with a green WhatsApp button: it opens a chat with your trainer, with the chapter already filled in. Add your question and send.

Your schedule · 14 days total

DaysTo doYou know it's done when...
1Chapters 01 + 02: understand Next.js, install the toolsnode --version prints a number
2Chapter 03: React (take the whole day, it is the foundation of everything)Exercise 01 done without looking at the solution
3Chapters 04 + 05 + 06: create the project, pages, navigationYour practice site has 4 pages linked together
4Chapters 07 + 08: layouts and stylingNav and footer everywhere, written once, and it looks like a real site
5Chapters 09 + 10: images and "use client"The FAQ items open and close
6Chapters 11 + 12 + 13: data, metadata, static exportnpm run build passes with no errors
7 to 12Chapter 14: THE REAL PROJECT, the company website your trainer gives you, one milestone per dayEach milestone checked before moving to the next
13Trainer feedback, corrections, polishThe build passes and the site looks right at phone width
14Delivery and presentation of the siteYou can explain every file in the project

The pace is intense on purpose: the course contains only what you will need for the real project. Nothing in it is optional.

Part 1 · Foundations

Chapter 01

What is Next.js, and why does it exist?

Goal: be able to explain in one sentence what Next.js is and why a team would choose it over plain HTML files.

Start from what you know

Imagine you build a website the classic way: one HTML file per page. index.html, about.html, contact.html. It works. But three problems appear very quickly:

  1. Repetition. Your navigation bar and footer are copied into every single file. Change one menu item and you must edit every page. With 30 pages, you will forget one, and the site becomes inconsistent.
  2. No components. You have a nice "card" design used 20 times. In plain HTML, that is 20 copies of the same markup. There is no way to say "here is my Card, reuse it".
  3. Data is painful. If your services live in a list somewhere, you cannot loop over that list in HTML. You write each item by hand.

React solves these problems. React is a JavaScript library that lets you build a page out of reusable pieces called components, and lets you generate HTML from data. We will learn just enough React in chapter 3.

So where does Next.js fit?

React alone is only a library. It does not answer questions like: How do URLs work? How do I get more than one page? How do I make the site fast? How do I ship it to a real server? Every team used to answer these questions themselves, differently, and often badly.

Next.js is a framework built on top of React that answers all of those questions for you. It gives you:

You needNext.js gives you
Multiple pages with clean URLsFile-based routing: create a folder, get a URL
Shared navigation and footerLayouts: write them once, every page gets them
A fast development experiencenpm run dev with instant reload when you save
Fast pages for visitorsPages are pre-built into plain HTML before anyone visits
Optimized images, fonts, metadataBuilt-in components and configuration
A way to shipOne command builds a folder you can host anywhere

The one-sentence definition

Next.js is a framework that takes React components and turns them into a complete, fast, multi-page website, with routing, layouts, and builds handled for you.

"Static" means pre-built

In this course we build a static website. Static means: when you run the build command, Next.js generates finished HTML, CSS, and JavaScript files. A visitor's browser just downloads those files. There is no database and no server code running per visitor. This makes the site extremely fast, very cheap to host, and hard to hack. It is perfect for portfolios, restaurants, agencies, landing pages, and documentation.

Check yourself: your client has a 25-page brochure site and complains that updating the footer takes an hour. Which Next.js feature fixes this?
Layouts. The footer is written once in a layout file, and all 25 pages share it. One edit updates every page. We build this in chapter 7.
Check yourself: is Next.js a replacement for React?
No. Next.js is built on top of React. You write React components; Next.js turns them into a full website with pages, URLs, and builds. You are always writing React when you write Next.js.

Chapter 02

Install your tools

Goal: a working Node.js installation, a code editor, and confidence in the terminal.

1. Node.js

Next.js runs on Node.js, which lets JavaScript run on your computer instead of only inside a browser. Download the LTS version (the stable one, currently 22.x) from nodejs.org and install it with the default options.

Then open a terminal and verify:

TerminalYou type this
$ node --version
v22.11.0
$ npm --version
10.9.0

If you see version numbers (yours may differ slightly), you are ready. npm came along automatically; it is the tool that downloads JavaScript packages, including Next.js itself.

2. A code editor

Use VS Code from code.visualstudio.com unless you already have a favorite. Recommended extensions: ES7+ React snippets and Prettier (auto-formats your code on save, so you never argue about spacing).

3. Terminal survival kit

You only need five commands for this entire course:

CommandWhat it does
cd my-folderMove into a folder ("change directory")
cd ..Move up one folder
ls (Mac/Linux) or dir (Windows)List what is in the current folder
npm run devStart the development server (you will type this daily)
Ctrl + CStop whatever is running in the terminal

Most common beginner mistake

Running commands in the wrong folder. If npm run dev says something like "no such file package.json", you are not inside your project folder. Run cd my-project first, then try again.

Chapter 03

React in 30 minutes

Goal: understand components, JSX, props, state, and rendering lists. This is all the React you need for this course.

Components: functions that return HTML

A React component is simply a JavaScript function whose name starts with a capital letter and which returns something that looks like HTML. That returned syntax is called JSX.

A minimal componentConcept
function Welcome() {
  return <h1>Hello, team!</h1>;
}

Once a component exists, you can use it like a custom HTML tag: <Welcome />. This is the superpower plain HTML never had: you invent your own tags.

JSX: five rules that cover 95% of cases

  1. Return one single root element. If you need several, wrap them in a <div> or in an empty wrapper <> ... </> (called a fragment).
  2. class becomes className, because class is a reserved word in JavaScript. Also for becomes htmlFor.
  3. Every tag must close. <img> becomes <img />, <br> becomes <br />.
  4. Curly braces { } mean "JavaScript goes here". Inside braces you can put a variable, a calculation, or a function call.
  5. Comments inside JSX look like {/* this */}, not <!-- this -->.
JSX with JavaScript insideConcept
function Greeting() {
  const name = "Sara";
  const hour = 14;
  return (
    <div className="greeting">
      {/* braces = escape hatch into JavaScript */}
      <h1>Hello, {name}!</h1>
      <p>{hour < 12 ? "Good morning" : "Good afternoon"}</p>
    </div>
  );
}

Props: passing data into a component

Components become truly reusable when you can pass them data, the same way HTML tags take attributes. These are called props (properties). The component receives them as one object, which we usually unpack directly:

Props make components reusableConcept
function ProfileCard({ name, job }) {
  return (
    <div className="card">
      <h2>{name}</h2>
      <p>{job}</p>
    </div>
  );
}

// Used three times, with different data each time:
function Team() {
  return (
    <section>
      <ProfileCard name="Sara" job="Designer" />
      <ProfileCard name="Ali" job="Developer" />
      <ProfileCard name="Lina" job="Project manager" />
    </section>
  );
}

One card design, written once, filled with different data. This is the "no more copy-paste" promise from chapter 1, delivered.

Rendering a list with .map()

When your data lives in an array, you turn each item into JSX with the array method .map(). Each generated element needs a key prop with a unique value, so React can track the items.

From array to HTMLConcept
const team = [
  { name: "Sara", job: "Designer" },
  { name: "Ali", job: "Developer" },
  { name: "Lina", job: "Project manager" },
];

function Team() {
  return (
    <section>
      {team.map((person) => (
        <ProfileCard key={person.name} name={person.name} job={person.job} />
      ))}
    </section>
  );
}

Now adding a team member means adding one line of data, not copying a block of HTML. Read that code slowly until it makes sense; it is the pattern you will use most in real work.

State: values that change when the user interacts

Props flow in from outside and do not change. When a component needs its own changing value (a counter, an open/closed menu, an input's text), it uses state via the useState function:

State with useStateInteractive
"use client"; // required in Next.js, explained in chapter 10
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}

Read it as: "give me a value count starting at 0, and a function setCount to change it." When setCount runs, React re-renders the component with the new value. You never touch the DOM yourself; you change the data and React updates the screen.

Live demo · this button is real, try it
Exactly the Counter component above, running here.
Practice 01~25 min · paper or any online React sandbox

Think in components

Show solution
Solution
function MenuItem({ name, price }) {
  return (
    <li>
      {name} · {price} €
    </li>
  );
}

const items = [
  { name: "Espresso", price: 2.5 },
  { name: "Cappuccino", price: 3.5 },
  { name: "Croissant", price: 2.0 },
];

function Menu() {
  return (
    <ul>
      {items.map((item) => (
        <MenuItem key={item.name} name={item.name} price={item.price} />
      ))}
    </ul>
  );
}

If your version differs slightly but renders three list items, it is correct.

Part 2 · Core Next.js

Chapter 04

Create the project, and a tour of every file

Goal: a running Next.js project on your machine, and you know what each file in it is for.

One command creates everything

TerminalYou type this
$ npx create-next-app@latest my-first-site

The installer asks a few questions. Exact wording changes between versions; answer with this table and you will match this course:

QuestionAnswerWhy
TypeScript?NoWe learn with plain JavaScript first. TypeScript comes later in your career.
ESLint / linter?YesIt warns you about mistakes while you type.
Tailwind CSS?NoWe learn real CSS first so you understand what any tool does underneath.
src/ directory?NoFewer folders to think about.
App Router?YesThe modern way. This whole course uses it.
Turbopack?YesFaster dev server. Accept the default.
Import alias?NoKeep the default.

Then start the development server:

TerminalYou type this
$ cd my-first-site
$ npm run dev

  ▲ Next.js
  - Local:  http://localhost:3000

Open http://localhost:3000 in your browser. You should see the Next.js welcome page. localhost:3000 means "a website served by my own computer, on port 3000". Only you can see it. Keep this terminal running while you work; press Ctrl + C when you want to stop it.

Tour of the project

my-first-site/ · what the installer created
app/← your website lives here. 90% of your time is in this folder
page.jsthe homepage ( / )
layout.jsthe frame around every page
globals.csssite-wide styles
favicon.icothe little browser-tab icon
public/images and files served as-is (chapter 9)
node_modules/downloaded packages. Never edit, never commit to git
package.jsonproject name, scripts, list of dependencies
next.config.mjsNext.js settings (we edit it once, in chapter 13)
jsconfig.json, eslint.config.mjstooling config. Ignore for now

Two files matter today. First, app/page.js is your homepage. A page is just a React component that is default exported:

app/page.jsReplace its content
export default function Home() {
  return (
    <main>
      <h1>My first Next.js site</h1>
      <p>It is alive!</p>
    </main>
  );
}

Save the file and look at the browser: it updates instantly, without you refreshing. This is called hot reload, and it is why the dev server stays running.

Second, app/layout.js is the frame around every page. Notice it renders {children}; that placeholder is where each page's content appears. We work with it properly in chapter 7.

The daily rhythm

Every work session from now on is: open the project folder in VS Code, open the terminal, run npm run dev, edit files, watch the browser update. That is the whole workflow.

Practice 02~20 min

Make it yours

Chapter 05

Pages and routing: folders become URLs

Goal: create new pages and predict exactly which URL any file will get.

The rule

Remember the hero diagram at the top of this course. Here is the complete rule:

The routing rule

Inside app/, every folder becomes a URL segment, and the folder's page.js file is what visitors see at that URL. No page.js in a folder = no page at that URL.

So to create an About page at /about, you create a folder app/about/ containing a file page.js:

app/about/page.jsNew file
export default function AboutPage() {
  return (
    <main>
      <h1>About us</h1>
      <p>We are a small team that loves the web.</p>
    </main>
  );
}

Save, then visit localhost:3000/about. That is all. No configuration file, no route registration. The folder structure is the configuration.

Folders nest, and so do URLs: app/services/design/page.js is served at /services/design.

Interactive · routing playground

Click a file. The fake browser shows the URL it would create.

localhost:3000/
The homepage.

Special file names inside app/

Next.js reserves a few file names. Each has one job:

FileJob
page.jsThe content of a page. Required for the URL to exist.
layout.jsShared frame around pages (chapter 7).
not-found.jsYour custom 404 page.

Others exist (loading.js, error.js...), but these three are all you need for this course. Any file with a different name (like hero.js or data.js) is ignored by the router: no risk of creating a URL by accident.

Practice 03~20 min

Grow the site to four pages

Show solution
Resulting structure
app/
page.js/
about/page.js/about
contact/page.js/contact
services/
web/page.js/services/web

Each page.js is a small default-exported component, like the About example above.

Chapter 06

Navigation with <Link>

Goal: connect your pages with fast, app-like navigation.

You could connect pages with a normal <a href="/about"> tag, and it would work. But a plain <a> makes the browser throw away the whole page and download the next one from scratch: you see a white flash, and it is slow.

Next.js provides a <Link> component instead. It looks and behaves like a link (visitors can right-click it, open in a new tab, and search engines follow it), but Next.js swaps the page content without a full reload, and even pre-loads pages the visitor is likely to click. Same HTML underneath, much faster experience.

app/page.jsEdit
import Link from "next/link";

export default function Home() {
  return (
    <main>
      <h1>My first Next.js site</h1>
      <p>
        Read more <Link href="/about">about us</Link>.
      </p>
    </main>
  );
}

Reading the import line

import Link from "next/link" means: "bring the Link component from the Next.js package into this file". Any component or function you use from another file must be imported at the top. If you forget, the error says "Link is not defined": that message is your reminder.

The rule of thumb: use <Link> for pages inside your site, and a normal <a> for external websites (like a link to Instagram).

A navigation component

A nav bar is the perfect first custom component. Create a components/ folder at the project root (next to app/, not inside it) to hold reusable pieces:

components/Nav.jsNew file
import Link from "next/link";

export default function Nav() {
  return (
    <nav>
      <Link href="/">Home</Link>
      <Link href="/about">About</Link>
      <Link href="/contact">Contact</Link>
    </nav>
  );
}

And use it in a page. Because Nav.js is our own file (not a package), the import path starts with dots that describe where the file is relative to the current one:

app/page.jsEdit
import Nav from "../components/Nav";

export default function Home() {
  return (
    <main>
      <Nav />
      <h1>My first Next.js site</h1>
    </main>
  );
}

../ means "go up one folder": from app/ up to the project root, then into components/. Getting import paths wrong is the number one beginner error; when it happens, the error message names the path it could not find, so check the dots first.

But wait: must we now import <Nav /> into every single page? That would be the copy-paste problem again. No. The next chapter fixes it properly.

Practice 04~15 min

Wire it up

Chapter 07

Layouts: build the frame once

Goal: a shared header and footer on every page, written exactly once.

Open app/layout.js. Simplified, it looks like this:

app/layout.jsThe root layout
import "./globals.css";

export const metadata = {
  title: "My first site",
  description: "Built while learning Next.js",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

Here is the mental model. The layout is a picture frame; each page is the picture. When someone visits /about, Next.js takes the layout and inserts the About page exactly where {children} is. Visit /contact, and the same frame gets the Contact page instead.

children is a prop that React fills automatically with whatever is nested inside. You never call it manually; you just mark where it belongs.

Put the nav and footer in the frame

app/layout.jsEdit
import "./globals.css";
import Nav from "../components/Nav";

export const metadata = {
  title: "My first site",
  description: "Built while learning Next.js",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <Nav />
        {children}
        <footer>© 2026 My first site</footer>
      </body>
    </html>
  );
}

Now delete <Nav /> (and its import) from your homepage: it would appear twice otherwise. Click through the site. Every page has the nav and the footer, and none of the page files mention them. This is the fix for the copy-paste problem from chapter 1. The 25-page footer update that took an hour is now a one-line edit.

One last fact to remember: the root layout is the only place in the whole app with <html> and <body> tags. Pages never include them.

Check yourself: you want to add a "Closed in August" banner visible on every page. Which file do you write it in?
In app/layout.js, just above {children}. One edit, every page shows it. If you thought "in every page", reread this chapter.
Practice 05~15 min

One frame for everything

Chapter 08

Styling: global CSS and CSS Modules

Goal: know where site-wide styles go, where component styles go, and why the two are separate.

Global CSS: the site-wide rules

app/globals.css is already imported by your root layout, so anything in it applies everywhere. This is the right place for foundations: fonts, background color, link colors, resets. Replace its content with something small and readable:

app/globals.cssReplace content
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: system-ui, sans-serif;
  line-height: 1.6;
  color: #1f2933;
  background: #ffffff;
}

main {
  max-width: 720px;
  margin: 0 auto;
  padding: 24px;
}

CSS Modules: styles that cannot leak

Here is a classic team accident: you write .card { padding: 20px } for the services page, and a month later a teammate names something else .card on the contact page. The styles collide and one page breaks mysteriously.

CSS Modules prevent this. Name a file Something.module.css and Next.js makes every class in it private to the component that imports it, by renaming classes behind the scenes to something unique like Nav_card__x7Kd2. Two files can both use .card and never collide.

components/Nav.module.cssNew file
.nav {
  display: flex;
  gap: 24px;
  padding: 16px 24px;
  border-bottom: 1px solid #e2e8f0;
}

.nav a {
  text-decoration: none;
  color: #1f2933;
  font-weight: 600;
}

.nav a:hover {
  color: #0e7c5b;
}
components/Nav.jsEdit
import Link from "next/link";
import styles from "./Nav.module.css";

export default function Nav() {
  return (
    <nav className={styles.nav}>
      <Link href="/">Home</Link>
      <Link href="/about">About</Link>
      <Link href="/contact">Contact</Link>
    </nav>
  );
}

Read the pattern: import the stylesheet as an object called styles, then use className={styles.nav}. The class name comes from JavaScript (that is why it sits in curly braces), and it maps to the .nav rule in the module file.

The team rule of thumb

Global CSS for foundations that should apply everywhere (fonts, colors, resets). A CSS Module next to each component for that component's own look. When in doubt, choose the module: leaked styles cause the most confusing bugs in team projects.

Practice 06~30 min

Make it look designed

Chapter 09

Images and the public/ folder

Goal: add images to the site the correct Next.js way.

The public/ folder

Anything you put in public/ is served from the root of your site, exactly as-is. A file at public/logo.png is available in the browser at /logo.png. Note that the URL does not include the word "public".

public/ maps to the site root
public/
logo.png→ yoursite.com/logo.png
images/
team.jpg→ yoursite.com/images/team.jpg

The <Image> component

You could use a plain <img> tag, but Next.js ships an upgraded <Image> component that lazy-loads images (they only download when scrolled into view) and prevents the page from jumping around while images load. That jump is a real ranking and usability problem called layout shift, and it is why width and height are required: they reserve the space in advance.

app/about/page.jsEdit
import Image from "next/image";

export default function AboutPage() {
  return (
    <main>
      <h1>About us</h1>
      <Image
        src="/images/team.jpg"
        alt="Our team standing in front of the office"
        width={720}
        height={480}
      />
    </main>
  );
}
  • src starts with / and is the path inside public/.
  • alt describes the image for blind visitors and for search engines. Write it like you are describing the photo on the phone to someone. Purely decorative image? Use alt="".
  • width and height are numbers in curly braces (they are JavaScript numbers, not strings). Use the image's real proportions.

Heads-up for chapter 13

On a normal server, <Image> also resizes and compresses images on the fly. A static export has no server, so in chapter 13 we will switch that one feature off with a single config line. Everything else about <Image> keeps working.

Practice 07~15 min

Add real images

Chapter 10

Server vs Client Components

Goal: know when a file needs "use client" at the top, and why most files do not.

Every component in the app/ folder is, by default, a Server Component: Next.js renders it to finished HTML ahead of time. That HTML is small and instant for the visitor. All the pages you have built so far are Server Components, and that is exactly right for a static site.

But some things can only happen in the visitor's browser: reacting to clicks, remembering state, reading the window size. A component that does these things must be a Client Component, and you mark it by writing one line at the very top of the file:

components/FaqItem.jsNew file · note line 1
"use client";

import { useState } from "react";

export default function FaqItem({ question, answer }) {
  const [open, setOpen] = useState(false);
  return (
    <div>
      <button onClick={() => setOpen(!open)}>{question}</button>
      {open && <p>{answer}</p>}
    </div>
  );
}

Two new things in that code: setOpen(!open) flips the value between true and false, and {open && <p>...</p>} means "render this paragraph only when open is true".

The decision is mechanical

The component...KindMark
shows text, images, links, lists of dataServer (default)nothing
uses useState, useEffect, or any onClick / onChangeClient"use client" on line 1
uses browser things like window or localStorageClient"use client" on line 1

In practice you do not even need to memorize this: if you use useState in a file without the marker, Next.js stops with an error that literally tells you to add "use client". Read errors; they are usually instructions.

Keep the amber small

Good Next.js style is to keep pages as Server Components and make only the small interactive piece a Client Component. The About page stays server-rendered; only the little FaqItem inside it carries "use client". Never paste "use client" at the top of every file "just in case": you would throw away the speed Next.js gives you for free.

And yes, Client Components still work in our static export. Their HTML is pre-built like everything else, and their JavaScript runs in the visitor's browser to make them interactive.

Check yourself: a Footer showing the year, and a mobile hamburger menu that opens on tap. Which needs "use client"?
Only the hamburger menu: it reacts to a tap and remembers open/closed state, so it needs useState and onClick. The footer just displays content, so it stays a Server Component with no marker.
Practice 08~30 min

Your first interactive component

Show solution for the bonus
components/FaqItem.js
<button onClick={() => setOpen(!open)}>
  {open ? "-" : "+"} {question}
</button>

The condition ? a : b pattern picks between two values. It is the same one from the Greeting example in chapter 3.

Chapter 11

From data to UI

Goal: keep content in one data file and render it with components, the way real sites are built.

Real websites separate content from presentation. The menu prices live in a data file; the components decide how they look. When the client says "the cappuccino is 4 euros now", you change one number, not any markup.

Create a data folder at the project root:

data/menu.jsNew file
export const menuItems = [
  { id: 1, name: "Espresso", price: 2.5, desc: "Short and strong." },
  { id: 2, name: "Cappuccino", price: 3.5, desc: "Creamy classic." },
  { id: 3, name: "Flat White", price: 3.8, desc: "Silky and smooth." },
  { id: 4, name: "Croissant", price: 2.0, desc: "Baked every morning." },
];

Note this is a named export (no default). A file can have many named exports, and you import them with curly braces matching the exact name.

Now a card component and a page that combines everything you have learned:

components/MenuCard.jsNew file
import styles from "./MenuCard.module.css";

export default function MenuCard({ name, price, desc }) {
  return (
    <article className={styles.card}>
      <div className={styles.top}>
        <h3>{name}</h3>
        <span className={styles.price}>{price.toFixed(2)} €</span>
      </div>
      <p>{desc}</p>
    </article>
  );
}
app/menu/page.jsNew file
import { menuItems } from "../../data/menu";
import MenuCard from "../../components/MenuCard";

export default function MenuPage() {
  return (
    <main>
      <h1>Our menu</h1>
      {menuItems.map((item) => (
        <MenuCard
          key={item.id}
          name={item.name}
          price={item.price}
          desc={item.desc}
        />
      ))}
    </main>
  );
}

Trace the flow out loud, it is the core loop of modern frontend work: data file → import the array → .map() over it → one component per item → props fill the component. Every product grid, team page, and pricing table you will ever build is this same pattern with different CSS.

Why key={item.id}?

React needs a stable identity for each item in a list, so it can update the right one when data changes. Use an id from the data. Avoid using the array index as key; it misbehaves when items are reordered or removed.

Practice 09~40 min

The data-driven page

Chapter 12

Metadata: titles, descriptions, and the browser tab

Goal: give every page its own title and description, which Google and social networks read.

Metadata is information about the page: the title in the browser tab, the description Google shows under a search result. In Next.js you never write <head> tags by hand; you export a metadata object and Next.js generates the tags.

You already have site-wide metadata in app/layout.js. Any page can export its own, which overrides the layout's for that page:

app/menu/page.jsAdd above the component
export const metadata = {
  title: "Our menu | The Little Café",
  description: "Espresso, cappuccino and fresh pastries in the heart of town.",
};

export default function MenuPage() {
  // ...same as before
}

Rules of thumb your future SEO colleagues will thank you for:

  • Every page gets a unique title, formatted like Page name | Site name.
  • The description is one honest, specific sentence. It is your ad in the search results.
Practice 10~10 min

Name every page

Part 3 · Ship it

Chapter 13

Static export: turn your project into a folder of files

Goal: build the site into plain HTML/CSS/JS files that can be hosted anywhere.

One config change

Tell Next.js you want a static export. Open next.config.mjs and add two settings:

next.config.mjsEdit
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: "export",
  images: { unoptimized: true },
};

export default nextConfig;
  • output: "export" means: at build time, generate a folder of finished static files.
  • images: { unoptimized: true } is the promise from chapter 9: on-the-fly image resizing needs a server, and a static site has none, so we switch that single feature off.

Build it

TerminalYou type this
$ npm run build

Route (app)
┌ ○ /
├ ○ /about
├ ○ /contact
└ ○ /menu

○  (Static)  prerendered as static content

Every ○ (Static) circle is a page that was pre-built into HTML. The result lands in a new folder called out/:

out/ · this folder IS your website
out/
index.htmlthe homepage
about.html/about
menu.html/menu
_next/CSS and JS bundles
images/copied from public/

Open out/about.html in your editor and look at it. It is real, finished HTML containing your text. This is what "static" meant in chapter 1: the work happened on your machine at build time, so the visitor's browser gets instant files.

Preview and host it

To preview the built site locally:

TerminalYou type this
$ npx serve out

To put it on the internet, upload the contents of out/ to any static host. Free options: drag the folder onto netlify.com/drop, or push the project to GitHub and import it into Vercel or Cloudflare Pages (they run npm run build for you on every push). No servers to manage, nothing to keep updated, nothing that can be hacked at 3 a.m.

What a static export cannot do

No login system, no database writes, no form handling on your own server (forms need an external service like Formspree, or a host feature like Netlify Forms). When a project needs those, you deploy the same Next.js app to a Node server instead of exporting, and unlock its server features. Same framework, bigger world: that is a later course.

Practice 11~15 min

Ship the practice site

Chapter 14 · The real project

Build a real company's website

Goal: deliver a complete static site for a real client, alone, from empty folder to exported build. This is your end-of-training deliverable.

This is no longer practice. Your trainer hands you the brief of a real company: its name, its business, its texts, its logo and its photos. Your job for days 7 to 12 is to turn that content into a finished website, applying exactly what you learned. Build it from scratch in a new project (do not reuse the practice project; starting from a blank folder is part of the job). Try to work without rereading the chapters, and treat them as your documentation when you get stuck.

The expected pages

The exact content comes from your trainer's brief, but a company site almost always follows this plan. Adapt the names if the brief asks for it:

PageURLMust contain
Home/Big heading with the company name, one photo, a short introduction, a Link styled as a button to the services
Services/servicesThe company's services or products from a data file via .map(), each as a styled card
About/aboutThe company's story, a team section from a data file, and 3 FAQ items that open and close
Contact/contactAddress, opening hours (from a data file), email, and an external map link

Site-wide requirements (this is what you will be evaluated on):

  • Nav and footer on every page, via the root layout, never repeated in a page file.
  • Every component styled with its own CSS Module; only foundations in globals.css.
  • Every image through <Image> with real alt text.
  • Unique metadata on every page.
  • Exactly one file in the whole project says "use client" (the FAQ item).
  • It builds with npm run build and works when served from out/.

Suggested project structure

company-site/ · target structure
app/
layout.jsNav + footer + default metadata
page.jsHome
globals.css
services/page.js
about/page.js
contact/page.js
components/Nav, Footer, ServiceCard, TeamMember, FaqItem + their .module.css files
data/services.js, team.js, info.js (address, hours...)
public/images/the photos and logo from the brief

One milestone per day

Professionals do not build everything at once. One milestone per day, verified in the browser before moving to the next. If a milestone is done early, get ahead on the next one. Check them off as you go:

Real project checklistDays 7 to 12

From empty folder to shipped site

Bonus goals, only if you finish early

  • Add a custom 404 page with app/not-found.js.
  • Group the services by category - hint: two arrays, or .filter() before .map().

How your trainer will review it

Not by pixel-perfection. The review questions are: Is the layout written once? Is content in data files? Are styles in modules? Is "use client" used exactly once and in the right place? Does the build pass? Those five answers show whether you think in Next.js.

Appendix

Appendix A

Cheat sheet

Commands

  • npx create-next-app@latest name new project
  • npm run dev develop at localhost:3000
  • npm run build build for production
  • npx serve out preview the static export
  • Ctrl + C stop the server

Routing

  • Folder in app/ = URL segment
  • page.js = the page at that URL
  • layout.js = shared frame, renders {children}
  • not-found.js = custom 404

Imports you will type daily

  • import Link from "next/link"
  • import Image from "next/image"
  • import { useState } from "react"
  • import styles from "./X.module.css"

JSX gotchas

  • className, not class
  • One root element per return (or <>...</>)
  • Self-close every tag: <Image />
  • { } = JavaScript inside markup
  • key on every .map() item

Server vs Client

  • Default = Server Component (fast, static)
  • Clicks or state? Add "use client" line 1
  • Keep client components small and low in the tree

Static export

  • output: "export" in next.config.mjs
  • images: { unoptimized: true }
  • Result in out/, host anywhere

Appendix B

Common errors, decoded

The error saysIt meansFix
"npm: command not found"Node.js is not installed or the terminal was open during installInstall Node LTS, then open a fresh terminal
"Could not read package.json"You are in the wrong foldercd into the project folder first
"X is not defined"You used a component or function without importing itAdd the import line at the top
"Module not found: Can't resolve '../components/Nav'"The import path or file name is wrongCount the ../ hops; check exact spelling and capitalization
"You're importing a component that needs useState..."State or events in a Server ComponentAdd "use client" as line 1 of that file
"Adjacent JSX elements must be wrapped..."Your return has two root elementsWrap them in one <div> or <>...</>
"Each child in a list should have a unique 'key' prop"A .map() without keysAdd key={item.id} to the outermost mapped element
Page shows 404 but the folder existsThe file inside is not named exactly page.jsRename it; only page.js creates a URL
Image shows broken iconWrong src pathPath starts at public/: file public/images/a.jpg is src="/images/a.jpg"
Port 3000 already in useAn old dev server is still runningFind that terminal and Ctrl + C, or accept the offered port 3001

General debugging method: read the first error (later ones are usually echoes of it), note the file name and line number it mentions, and re-read that exact line. Nine times out of ten it is a typo, a missing import, or a missing closing tag.

Appendix C

Glossary

Component
A capitalized JavaScript function that returns JSX. The reusable building block of React.
JSX
The HTML-like syntax inside components. Curly braces embed JavaScript.
Props
Data passed into a component like HTML attributes, making it reusable with different content.
State
A component's own changing value, created with useState. Changing it re-renders the component.
App Router
Next.js's routing system where folders in app/ define URLs.
Layout
A shared frame around pages. Renders each page where it says {children}.
Server Component
The default. Rendered to HTML ahead of time; no interactivity of its own.
Client Component
A component marked "use client" whose JavaScript runs in the browser for interactivity.
CSS Module
A .module.css file whose class names are private to the component importing it.
Hot reload
The dev server updating the browser instantly when you save a file.
Static export
Building the whole site into plain files in out/, hostable on any static host.
Build
The process (npm run build) that turns your source code into optimized production files.
localhost:3000
Your own computer serving the site during development. Only visible to you.
npm
The package manager that installs libraries and runs project scripts.