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.
The single most important idea in this course
In Next.js, folders are your URLs. If you understand this one sentence, everything else in this course is detail.
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
| Days | To do | You know it's done when... |
|---|---|---|
| 1 | Chapters 01 + 02: understand Next.js, install the tools | node --version prints a number |
| 2 | Chapter 03: React (take the whole day, it is the foundation of everything) | Exercise 01 done without looking at the solution |
| 3 | Chapters 04 + 05 + 06: create the project, pages, navigation | Your practice site has 4 pages linked together |
| 4 | Chapters 07 + 08: layouts and styling | Nav and footer everywhere, written once, and it looks like a real site |
| 5 | Chapters 09 + 10: images and "use client" | The FAQ items open and close |
| 6 | Chapters 11 + 12 + 13: data, metadata, static export | npm run build passes with no errors |
| 7 to 12 | Chapter 14: THE REAL PROJECT, the company website your trainer gives you, one milestone per day | Each milestone checked before moving to the next |
| 13 | Trainer feedback, corrections, polish | The build passes and the site looks right at phone width |
| 14 | Delivery and presentation of the site | You 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.
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:
- 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.
- 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".
- 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 need | Next.js gives you |
|---|---|
| Multiple pages with clean URLs | File-based routing: create a folder, get a URL |
| Shared navigation and footer | Layouts: write them once, every page gets them |
| A fast development experience | npm run dev with instant reload when you save |
| Fast pages for visitors | Pages are pre-built into plain HTML before anyone visits |
| Optimized images, fonts, metadata | Built-in components and configuration |
| A way to ship | One 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?
Check yourself: is Next.js a replacement for React?
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:
$ node --version
v22.11.0
$ npm --version
10.9.0If 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:
| Command | What it does |
|---|---|
cd my-folder | Move 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 dev | Start the development server (you will type this daily) |
Ctrl + C | Stop 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.
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
- Return one single root element. If you need several, wrap them in a
<div>or in an empty wrapper<> ... </>(called a fragment). classbecomesclassName, becauseclassis a reserved word in JavaScript. AlsoforbecomeshtmlFor.- Every tag must close.
<img>becomes<img />,<br>becomes<br />. - Curly braces
{ }mean "JavaScript goes here". Inside braces you can put a variable, a calculation, or a function call. - Comments inside JSX look like
{/* this */}, not<!-- this -->.
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:
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.
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:
"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.
Think in components
Show 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.
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
$ npx create-next-app@latest my-first-siteThe installer asks a few questions. Exact wording changes between versions; answer with this table and you will match this course:
| Question | Answer | Why |
|---|---|---|
| TypeScript? | No | We learn with plain JavaScript first. TypeScript comes later in your career. |
| ESLint / linter? | Yes | It warns you about mistakes while you type. |
| Tailwind CSS? | No | We learn real CSS first so you understand what any tool does underneath. |
src/ directory? | No | Fewer folders to think about. |
| App Router? | Yes | The modern way. This whole course uses it. |
| Turbopack? | Yes | Faster dev server. Accept the default. |
| Import alias? | No | Keep the default. |
Then start the development server:
$ cd my-first-site
$ npm run dev
▲ Next.js
- Local: http://localhost:3000Open 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
Two files matter today. First, app/page.js is your homepage. A page is just a React component that is default exported:
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.
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:
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.
Click a file. The fake browser shows the URL it would create.
Special file names inside app/
Next.js reserves a few file names. Each has one job:
| File | Job |
|---|---|
page.js | The content of a page. Required for the URL to exist. |
layout.js | Shared frame around pages (chapter 7). |
not-found.js | Your 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.
Grow the site to four pages
Show solution
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.
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:
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:
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.
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:
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
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?
app/layout.js, just above {children}. One edit, every page shows it. If you thought "in every page", reread this chapter.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:
* {
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.
.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;
}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.
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".
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.
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>
);
}srcstarts with/and is the path insidepublic/.altdescribes 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? Usealt="".widthandheightare 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.
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:
"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... | Kind | Mark |
|---|---|---|
| shows text, images, links, lists of data | Server (default) | nothing |
uses useState, useEffect, or any onClick / onChange | Client | "use client" on line 1 |
uses browser things like window or localStorage | Client | "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"?
useState and onClick. The footer just displays content, so it stays a Server Component with no marker.Your first interactive component
Show solution for the bonus
<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:
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:
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>
);
}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.
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:
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.
Name every page
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:
/** @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
$ npm run build
Route (app)
┌ ○ /
├ ○ /about
├ ○ /contact
└ ○ /menu
○ (Static) prerendered as static contentEvery ○ (Static) circle is a page that was pre-built into HTML. The result lands in a new folder called out/:
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:
$ npx serve outTo 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.
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:
| Page | URL | Must contain |
|---|---|---|
| Home | / | Big heading with the company name, one photo, a short introduction, a Link styled as a button to the services |
| Services | /services | The company's services or products from a data file via .map(), each as a styled card |
| About | /about | The company's story, a team section from a data file, and 3 FAQ items that open and close |
| Contact | /contact | Address, 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 realalttext. - Unique
metadataon every page. - Exactly one file in the whole project says
"use client"(the FAQ item). - It builds with
npm run buildand works when served fromout/.
Suggested project structure
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:
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 A
Cheat sheet
Commands
npx create-next-app@latest namenew projectnpm run devdevelop at localhost:3000npm run buildbuild for productionnpx serve outpreview the static exportCtrl + Cstop the server
Routing
- Folder in
app/= URL segment page.js= the page at that URLlayout.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, notclass- One root element per return (or
<>...</>) - Self-close every tag:
<Image /> { }= JavaScript inside markupkeyon 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.mjsimages: { unoptimized: true }- Result in
out/, host anywhere
Appendix B
Common errors, decoded
| The error says | It means | Fix |
|---|---|---|
| "npm: command not found" | Node.js is not installed or the terminal was open during install | Install Node LTS, then open a fresh terminal |
| "Could not read package.json" | You are in the wrong folder | cd into the project folder first |
| "X is not defined" | You used a component or function without importing it | Add the import line at the top |
| "Module not found: Can't resolve '../components/Nav'" | The import path or file name is wrong | Count the ../ hops; check exact spelling and capitalization |
| "You're importing a component that needs useState..." | State or events in a Server Component | Add "use client" as line 1 of that file |
| "Adjacent JSX elements must be wrapped..." | Your return has two root elements | Wrap them in one <div> or <>...</> |
| "Each child in a list should have a unique 'key' prop" | A .map() without keys | Add key={item.id} to the outermost mapped element |
| Page shows 404 but the folder exists | The file inside is not named exactly page.js | Rename it; only page.js creates a URL |
| Image shows broken icon | Wrong src path | Path starts at public/: file public/images/a.jpg is src="/images/a.jpg" |
| Port 3000 already in use | An old dev server is still running | Find 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.cssfile 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.