Next.js tutorial: your first page, route, and fetch
A beginner Next.js tutorial: build a small coffee menu with the App Router. Create a page, a layout, a dynamic route, and fetch data on the server.

How do you go from an empty Next.js project to pages that link together and load real data?
Short answer: in the App Router, every folder inside app/ is a URL and every page.tsx is the page at that URL. A shared layout.tsx wraps them all, Link moves between them, and a page can be an async function that fetches its data before it renders.
In this tutorial you will build a tiny coffee menu called Bean There, step by step: a home page, a shared layout with navigation, a menu page, a detail page for each coffee, and a menu that loads from an API. It assumes you know basic React components. If you already know React and want to know what is different, Next.js after React covers that instead.
What you are building
By the end, the app/ folder will look like this:
app/
layout.tsx → wraps every page
page.tsx → /
menu/
coffees.ts → data, not a page
page.tsx → /menu
loading.tsx → shown while /menu loads
[slug]/
page.tsx → /menu/yirgacheffe, /menu/huila, ...
Read it top to bottom and you can already see the site's URLs. That is the main idea of the App Router, and Project Structure (opens in a new tab) walks through each file.
You can follow along in the course, which runs in your browser. To run it on your own machine instead, npx create-next-app@latest creates a project with this app/ folder already set up.
Step 1: your first page
Open app/page.tsx and replace what is there:
export default function HomePage() {
return (
<main>
<h1>Bean There</h1>
<p>Small-batch coffee, roasted this week.</p>
</main>
)
}That is the page at /. A page is a React component that is the default export of a file named page.tsx. The function name doesn't matter. The file name does. See Your First Page (opens in a new tab).
Step 2: a shared layout with navigation
Every page needs the same header. Put it in app/layout.tsx, the root layout:
import type { Metadata } from "next"
import Link from "next/link"
export const metadata: Metadata = {
title: "Bean There",
description: "A small coffee menu built with Next.js",
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<nav>
<Link href="/">Home</Link> · <Link href="/menu">Menu</Link>
</nav>
{children}
</body>
</html>
)
}Three things are happening here:
childrenis whichever page is being shown. The layout stays, and the page inside it changes.- The root layout is the only place you write
<html>and<body>. Next.js requires them here. metadatasets the page title and description. You don't edit aheadtag yourself.
Link comes from next/link. It works like an a tag, but moves between pages without reloading the whole site. See The Root Layout (opens in a new tab) and Linking With Link (opens in a new tab).
Step 3: a second route
The Menu link goes nowhere yet. First, add some data in app/menu/coffees.ts:
export type Coffee = {
slug: string
name: string
origin: string
price: number
}
export const coffees: Coffee[] = [
{ slug: "yirgacheffe", name: "Yirgacheffe", origin: "Ethiopia", price: 18 },
{ slug: "huila", name: "Huila", origin: "Colombia", price: 16 },
{ slug: "santos", name: "Santos", origin: "Brazil", price: 14 },
]This file sits inside app/menu/, but it isn't a page, because it isn't named page.tsx. Only page files become URLs.
Now create app/menu/page.tsx:
import Link from "next/link"
import { coffees } from "./coffees"
export default function MenuPage() {
return (
<main>
<h1>Menu</h1>
<ul>
{coffees.map((coffee) => (
<li key={coffee.slug}>
<Link href={`/menu/${coffee.slug}`}>{coffee.name}</Link> · ${coffee.price}
</li>
))}
</ul>
</main>
)
}Creating the folder created the route. There is no router config to update. /menu now works, and the nav link reaches it. File Based Routing (opens in a new tab) has more on how folders map to URLs.
Step 4: a page for every coffee
You don't want a folder per coffee. Instead, make one dynamic route. A folder name in square brackets matches any value in that part of the URL. Create app/menu/[slug]/page.tsx:
import { notFound } from "next/navigation"
import { coffees } from "../coffees"
export default async function CoffeePage({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const coffee = coffees.find((c) => c.slug === slug)
if (!coffee) notFound()
return (
<main>
<h1>{coffee.name}</h1>
<p>
From {coffee.origin}. ${coffee.price} a bag.
</p>
</main>
)
}Visit /menu/huila and slug is "huila". Visit /menu/santos and it is "santos". One file handles every coffee.
In current versions of Next.js, params is a promise, which is why the page is async and awaits it. If someone visits a coffee that doesn't exist, notFound() shows the 404 page instead of crashing. See Dynamic Segments And Params (opens in a new tab).
Step 5: fetch the menu from an API
Real menus come from a server, not a file. A page in the App Router can be an async function, so it can fetch data before it renders. The URL below is a placeholder, so swap in your own API:
import Link from "next/link"
import type { Coffee } from "./coffees"
export default async function MenuPage() {
const res = await fetch("https://example.com/api/coffees")
if (!res.ok) throw new Error("Could not load the menu")
const coffees: Coffee[] = await res.json()
return (
<main>
<h1>Menu</h1>
<ul>
{coffees.map((coffee) => (
<li key={coffee.slug}>
<Link href={`/menu/${coffee.slug}`}>{coffee.name}</Link> · ${coffee.price}
</li>
))}
</ul>
</main>
)
}This runs on the server. The HTML that reaches the browser already has the coffees in it, with no empty list to fill in afterwards. Your First Server Fetch (opens in a new tab) is this step in the course, and How REST APIs work explains what is on the other end of that fetch.
While the fetch is running, you can show something. Add app/menu/loading.tsx:
export default function Loading() {
return <p>Loading the menu…</p>
}Next.js shows it automatically while /menu waits for its data. See Loading UI (opens in a new tab).
The special files at a glance
| File | What it does | Example URL |
|---|---|---|
app/page.tsx | The home page | / |
app/layout.tsx | Wraps every page. Holds html, body and shared UI | Every URL |
app/menu/page.tsx | A page at a folder's path | /menu |
app/menu/[slug]/page.tsx | One page for many URLs | /menu/huila |
app/menu/loading.tsx | Shown while that route loads | /menu, while waiting |
Any other file, like coffees.ts | Not a route. Just code you import | None |
Common mistakes
- Naming the file wrong. It has to be
page.tsx, lowercase.index.tsx,Page.tsxormenu.tsxwon't create a route. - Leaving
htmlandbodyout of the root layout. The root layout must render both. Nested layouts must not. - Using a plain
atag for internal links. It works, but reloads the whole page. UseLinkfromnext/link. - Reading
params.slugwithout awaitingparams. In current versions,paramsis a promise. Make the pageasyncandawait paramsfirst. - Adding
useStateoronClickto a page. Pages are Server Components by default, so they can't use browser-only hooks. Move the interactive part into its own file that starts with"use client". Next.js after React explains why.
Quick recap
- A folder in
app/is a URL, andpage.tsxis the page at that URL. layout.tsxwraps pages with shared UI, and the root layout holdshtmlandbody.Linkmoves between pages without a full reload.- A
[slug]folder makes one page handle many URLs. - Pages can be
asyncand fetch data on the server, andloading.tsxcovers the wait.
Frequently asked questions
Is Next.js hard to learn?
Not if you already know basic React. The new parts are the file-based routing and where your components run, and both are covered in this tutorial. If React itself is new, start with React Fundamentals (opens in a new tab).
What is the App Router in Next.js?
It is the routing system built around the app/ folder, where folders are URLs and special files like page.tsx and layout.tsx define what each URL shows. It is the default for new Next.js projects.
How do I create a new page in Next.js?
Create a folder inside app/ with the name you want in the URL, and put a page.tsx file in it with a default-exported component. app/about/page.tsx becomes /about.
How do dynamic routes work in Next.js?
A folder name in square brackets, like [slug], matches any value in that part of the URL. The page receives that value in params, and you use it to look up the right data.
Do I need to know React before Next.js?
Yes. Next.js is built on React, so you should be comfortable with components, props and lists first. React Fundamentals (opens in a new tab) covers what you need.
Practice
The matching exercises are in the course. Start with Creating Pages (opens in a new tab), build a list-to-detail flow in Linking List To Detail (opens in a new tab), then load real data in An API Backed Gallery (opens in a new tab). Each exercise works in your browser, so there is nothing to install.
Keep going
Next.js Fundamentals (opens in a new tab) takes Bean There's ideas further: Server and Client Components, route handlers, Server Actions, and caching. If components and props are still new, start with React Fundamentals (opens in a new tab).







