Your first Next.js app
Build a small notes app in Next.js step by step: a root layout, two pages, a link, notes loaded on the server, and a form that saves with a Server Action.
- 01Build pages inside a layoutWrite a root layout and two pages, and link them, so every page shares the same frame.
- 02Load and save data on the serverRead notes in an async Server Component, and save a new one from a form with a Server Action.
- 03Build and run the appRead what next build and next start print, and tell them apart from next dev.
A notes app, one step at a time
In this lesson you build a small notes app with Next.js. It has two pages: / lists your notes, and /new has a form to write a new one. Both pages share a row of links at the top. The notes are loaded on the server, and the form saves a note on the server too. You build it in five steps, and each step adds one idea:
- A root layout and a first page.
- A second page, inside the same layout.
- Links between the two pages.
- Notes loaded on the server.
- A form that saves a note.
Then you build the app for real and run it. Each idea gets a first look here, and a stage of the course of its own later; the text links to it. This is the app you'll have at the end:
/ reads the notes from notes.ts, and the form at /new saves a note through actions.ts.Every step below is a real Next.js 16.4.3 project. It was built with next build, run with next start, and what it sent back was recorded. To follow along on your computer, make a project with npx create-next-app@latest notes, replace the files in its app folder with the step's files, and run npm run dev. Creating a Next.js app explains the starter's questions and its files.
Step 1 is two files. app/layout.tsx is the root layout: the component around every page. app/page.tsx is the page for /. Click a file in the tree to read it, then look at the result under it.
app/page.tsxexport default function Home() { return ( <main> <h1>All notes</h1> <p>No notes yet.</p> </main> );}No notes yet.
The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.
In app/page.tsx, Home returns the page: a heading on line 4 and a short message on line 5. The file exports it with export default, which is how Next.js finds the page's component. Its place, right inside app, makes it the page for /.
Now open app/layout.tsx. Lines 5 and 6 give every page its <html> and <body>; a Next.js app needs this file, and it must have both tags. Line 7 is a <header> that every page will show. Line 8, {children}, is where the page goes. LayoutProps<"/"> on line 3 is a type that Next.js writes for you when it builds or runs the app: it says the layout gets children. Line 1 exports metadata, and its title becomes the page's <title>: the browser frame shows Notes next to the address.
The Browser tab shows the page: Notes app, then All notes and No notes yet. The Terminal tab shows what next build printed. Its route table lists ○ /, your page, and ○ /_not-found, the page Next.js adds for addresses that don't exist. The legend under the table says what ○ means: prerendered as static content. Next.js made the page's HTML ahead of time and keeps it, to send to every visitor. In this recording, that first copy was made during the build. A stored copy can be made again later, and step 5 shows how.
A café has one front and one sign, and several rooms behind them. Guests walk from room to room through doors. The food comes from a pantry that guests never see. To order, a guest fills in a slip and hands it to the kitchen; the staff do the rest, and the guest sees the dish arrive.
- In real life: The café's front and its sign, the same whichever room you sit in
- In Next.js: The root layout, around every page
- In real life: Each room, with its own door
- In Next.js: Each page, with its own address
- In real life: The pantry, where only the staff go
- In Next.js:
notes.ts, which only runs on the server - In real life: An order slip you hand to the kitchen
- In Next.js: The form, sent to a Server Action
A Next.js app is files in the app folder. A layout goes around every page, a page.tsx makes each address, Server Components load the data, and Server Actions save it. next build turns the files into an app, and next start runs it.
A second page in the same layout
Step 2 adds the page for writing a note. Its address is /new, so its file goes in a new folder, app/new, and is named page.tsx. Nothing else changes.
app/new/page.tsxexport default function NewNotePage() { return ( <main> <h1>New note</h1> <p>The form goes here.</p> </main> );}No notes yet.
The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.
app/new/page.tsx has the same shape as the home page. Line 4 is its heading, New note, and line 5 holds the place where the form will go in step 5. The folder new gives the address /new: one folder for each part of the address. (Routing and navigation starts with how that works.)
The Terminal tab now lists three routes: ○ /, ○ /_not-found and ○ /new. In the Browser tab, pick /new above the page. It starts with Notes app, like /, though app/new/page.tsx never says so: the header comes from the layout. Next.js puts each page inside the root layout, where {children} is.
Notes app header comes from app/layout.tsx, so both pages have it. Under it, where the layout puts {children}, each address shows its own page.That's what a layout is for: the parts every page shares, written once. Later lessons add layouts inside folders too, around only the pages in that folder.
Links between the pages
The two pages exist, but a visitor can only reach /new by typing its address. Step 3 puts links in the layout, so every page has them.
app/layout.tsximport Link from "next/link"; export const metadata = { title: "Notes" }; export default function RootLayout({ children }: LayoutProps<"/">) { return ( <html lang="en"> <body> <nav> <Link href="/">All notes</Link> <Link href="/new">New note</Link> </nav> {children} </body> </html> );}No notes yet.
The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.
Line 1 imports Link from next/link, a component that comes with Next.js. Line 10 uses it twice: href="/" goes to the list, and href="/new" to the form. The <nav> around them, on lines 9 to 11, takes the place of the old header.
Click New note in the recorded page: the browser frame switches to the recorded /new, and All notes switches back. Then open the Network tab. Each Link arrived as an ordinary link, <a href="/new">New note</a>, so it works even before any JavaScript has run.
The Steps tab shows what happens once the JavaScript runs. The recorder opened / in real Chrome, then clicked New note. Step 1 is one page load, GET /. While the page was on the screen, Next.js's router also asked for /new with fetch requests marked next-router-prefetch: it loaded the linked page's static parts ahead of time. That's called prefetching. Step 2, the click, made no page load and no request at all: the address changed to /new and the page showed The form goes here. from what was already loaded. That's why you use Link instead of a plain <a> for pages of your own app. The Linking and navigating lesson shows both.
Notes from the server
Step 4 gives the list some notes. A real app keeps them in a database. This one keeps them in a file, app/notes.ts, that stands in for one, and the home page reads them.
app/page.tsximport { getNotes } from "./notes"; export default async function Home() { const notes = await getNotes(); return ( <main> <h1>All notes</h1> <ul> {notes.map((note) => ( <li key={note.id}>{note.text}</li> ))} </ul> </main> );}- Buy mint tea
- Call Asha
The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.
Open app/notes.ts first. Lines 2 to 5 are the notes: an array of two objects, each with an id and a text. Lines 7 to 9 are getNotes(), which returns them. It's an async function, like a real database call, so the code that uses it stays the same when a database takes its place.
Back in app/page.tsx, line 1 imports getNotes. Line 3 makes Home an async function, so line 4 can wait for the notes with await. Lines 9 to 11 turn each note into a list item with map, using its id as the key.
A React component that is async? That works because Home is a Server Component: a component that runs only on the server, never in the browser, so it can wait for data while it renders. In Next.js, layouts and pages start as Server Components. The client part of an app starts at a file with "use client" at the top: its components, and everything that file imports, go to the browser too. This app has no such file. (Server Components, in the React course, explains them.) There's no state, no Effect and no loading message here.
Open the Network tab: the HTML for / already holds <li>Buy mint tea</li> and <li>Call Asha</li>. The Terminal still says ○ /. During next build, Next.js ran Home, waited for getNotes(), and stored the HTML. The header x-nextjs-cache: HIT says the server answered with that stored copy.
Here the notes come back at once, from the server's memory, so Next.js could make the page during the build. A real database answers over the network, and its answer changes as people write notes. Connecting a database shows how to read one, and the Rendering and caching stage how Next.js decides what to store.
A form that saves a note
Step 5 makes /new work. It changes three files: notes.ts learns to save a note, a new file, app/actions.ts, holds the function the form calls, and app/new/page.tsx gets the form. Start with the new file.
app/actions.ts"use server"; import { revalidatePath } from "next/cache";import { redirect } from "next/navigation";import { saveNote } from "./notes"; export async function addNote(formData: FormData) { const text = String(formData.get("text") ?? "").trim(); if (text) await saveNote(text); revalidatePath("/"); redirect("/");}- Buy mint tea
- Call Asha
The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.
Line 1 of app/actions.ts is "use server". It marks every function this file exports as a Server Function: a function that runs on the server, which the page can still call from the browser. Next.js gives each one an id, and a call becomes a request to the server. A Server Function that a form calls, to change data, is called a Server Action. (Server Functions, in the React course, explains them.)
addNote, on lines 7 to 12, is the action. It gets the form's fields as formData, a FormData object. Line 8 reads the field named text: String(… ?? "") makes sure it's a string even if the field is missing, and trim() removes spaces at both ends. Line 9 saves it with saveNote, but only if there's any text. Line 10, revalidatePath("/"), tells Next.js that the HTML it stored for / is out of date. Line 11, redirect("/"), sends the visitor to the list.
Now open app/new/page.tsx. Line 1 imports addNote, and line 7 hands it to the form: <form action={addNote}>. That's React's way to connect a form to a function (Form actions). Line 8 is the box, named text, which is the name line 8 of the action reads; aria-label gives the box a name for screen readers. Line 9 is the Save button. In app/notes.ts, lines 11 to 13 are saveNote, which adds the note to the array.
The recorder used this form the way a browser does before the page's JavaScript has loaded. Open the Network tab and pick /new: the HTML has <form action="" enctype="multipart/form-data" method="POST">, and inside it a hidden field, $ACTION_ID_ followed by a long code. React wrote that field: it's the id of addNote. Then pick POST /new, press Save: the recorder typed Water the plants and sent the form. The answer is 303, with location: / and no page at all. Last, pick /, after saving: three notes, and x-nextjs-cache: MISS, which says Next.js made this HTML again instead of using the stored copy.
/new, and Next.js calls addNote, which saves the note and marks / as out of date. The 303 answer sends the browser to /, which Next.js makes again with three notes.Step through it below. Before you start, guess: after which step does the visitor see the new note?
Press Saveafter step 1All notes New note
New note
Write a note Save
React isn't running yet: clicks do nothing.
The page isn't complete yet.
The page at /new shows the form. The visitor types Water the plants into the box and presses Save.
The note shows after step 7, the last one. Everything before it happened on the server, or on the way there and back. Forms that call Server Actions work before the page's JavaScript has loaded, which is why the recorder could save a note with none at all.
Most visitors press Save after the JavaScript has loaded, and then the trip is different. The recorder did that too, in real Chrome: it opened /new, typed Water the plants, and pressed Save. Open the Steps tab and pick the last step.
app/new/page.tsximport { addNote } from "../actions"; export default function NewNotePage() { return ( <main> <h1>New note</h1> <form action={addNote}> <input name="text" aria-label="Note" placeholder="Write a note" /> <button type="submit">Save</button> </form> </main> );}Each step was taken in real Chrome against next start. Pick one to see the page after it.
- Buy mint tea
- Call Asha
- Water the plants
- Focus
- nothing: the page itself
- Scrolled
- 0 px down
- Announced to screen readers
All notes
Requests the page made during this step (1), without the files it loaded
POST/new200text/x-component- next-router-state-tree
- %5B%22%22%2C%7B%22children%22%3A%5B%22new%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%2Cnull%2Cnull%2C514%5D%7D%2Cnull%2Cnull%2C514%5D%7D%2Cnull%2Cnull%2C530%5D
- next-action
- 40f464c21bee605d99e71bab019b8a06b0082be0f4
Pressing Save made one request, and no page load: a fetch that posted the form to /new with a next-action header, the action's id. It answered 200 with text/x-component, not HTML. That answer is an RSC payload: data that describes what the Server Components rendered, for React to put on the screen. With it, the address changed to / and the list showed three notes, without the browser loading a new page. The docs describe it this way: an action returns the updated UI and data in one round trip. The Fetching and mutating data stage looks inside that answer.
What does line 10 really do? Try the app without it. Each button below is one version of app/actions.ts, built and run the same way. Before you click, guess what / lists after Save without revalidatePath.
app/actions.tsNext.jsapp/actions.ts"use server"; import { revalidatePath } from "next/cache";import { redirect } from "next/navigation";import { saveNote } from "./notes"; export async function addNote(formData: FormData) { const text = String(formData.get("text") ?? "").trim(); if (text) await saveNote(text); revalidatePath("/"); redirect("/");}- Buy mint tea
- Call Asha
- Water the plants
The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.
Line 10 tells Next.js that the stored HTML of / is out of date. After the redirect, Next.js makes / again (x-nextjs-cache: MISS), and the list shows Water the plants.
Without revalidatePath, the note is saved, but / still shows two notes, with x-nextjs-cache: HIT. Next.js made the HTML of / during the build, and keeps sending that copy until something says it's out of date. Saving data and showing it again are two separate jobs. Updating the UI after a mutation covers each way to show fresh data after a change.
A Server Action is a request to your server, and anyone can send one, not only your form. Next.js checks that the request comes from a page on the same site (the browser's origin header), but who the visitor is, and whether they may save, is up to your code. A real app checks that inside every action. The Building real apps stage covers sign-in and security.
Building and running it for real
While you write code, npm run dev runs next dev: a server that makes each page again whenever you ask for it, so every change you save shows at once. Visitors get something else: the app that next build makes, run by next start. The package.json in the file tree has a script for each: dev, build and start. Each row is one command:
| Command | What it does | When you use it |
|---|---|---|
next dev | Starts a server for writing code. It makes each page again when you ask for it, so you see every change you save. | While you build the app, on your own computer. |
next build | Bundles the code, checks the types, and makes the HTML of every page it can make ahead of time. It prints the route table. | Once, before the app goes live. |
next start | Runs the app that next build made, and answers requests with it. It doesn't build anything. | On the server that visitors reach, after next build. |
Here is the finished app once more, with its Terminal tab open. It shows both commands, as the recorder ran them.
app/actions.ts"use server"; import { revalidatePath } from "next/cache";import { redirect } from "next/navigation";import { saveNote } from "./notes"; export async function addNote(formData: FormData) { const text = String(formData.get("text") ?? "").trim(); if (text) await saveNote(text); revalidatePath("/"); redirect("/");}$ next build▲ Next.js 16.4.3 (Turbopack)- Cache Components enabled- Partial Prefetching enabled Creating an optimized production build ...✓ Compiled successfully Running TypeScript ... Finished TypeScript ... Collecting page data ...✓ Generating static pages (4/4) Finalizing page optimization ... Route (app)┌ ○ /├ ○ /_not-found└ ○ /new ○ (Static) prerendered as static content $ next start▲ Next.js 16.4.3- Local: http://127.0.0.1:3000- Network: http://127.0.0.1:3000✓ ReadyRead the marked lines of next build from the top:
✓ Compiled successfully: Turbopack, Next.js's bundler, turned your files into the JavaScript the server and the browser run.Finished TypeScript: the build checked your types. A type error stops the build here, before anything goes live.✓ Generating static pages (4/4): Next.js ran the pages it could make ahead of time and stored their HTML.- The route table:
○ /,○ /_not-foundand○ /new. The form and the action didn't change any symbol:/newis still prerendered as static content, and the action runs only when someone sends the form.
Under $ next start, the server printed its address and ✓ Ready: from then on, it answers requests with what the build made. (The recorder asked it to listen only on this computer, 127.0.0.1; started on its own, it prints localhost.) next start builds nothing, so after every change you run next build again.
Putting the app online means running those two commands on a computer that visitors can reach, or giving the project to a hosting platform that runs them for you. One thing changes when you do: these notes live in the server's memory. They're gone when the server restarts, and two copies of the server would each have their own list. That's why real apps keep their data in a database. Deploying Next.js covers the ways to deploy, from your own Node.js server to a static export.
Which file does which job
Every Next.js app, however big, is made of the same kinds of files you wrote here. Each row is one step: the files it added, and what its recording shows.
| Step | Files you wrote | What the recording shows |
|---|---|---|
| 1. A root layout and a page | app/layout.tsx and app/page.tsx | ○ /: one page, under the layout's Notes app header. |
| 2. A second page | app/new/page.tsx | ○ /new: a second page, with the same header. |
| 3. Links | app/layout.tsx again | <a href="/"> and <a href="/new"> in the HTML of both pages. |
| 4. Data from the server | app/notes.ts, and app/page.tsx again | Buy mint tea and Call Asha in the HTML of /. |
| 5. A form that saves | app/actions.ts, and app/new/page.tsx and app/notes.ts again | POST /new answered 303, and / then lists three notes. |
A bigger app has more of each: more pages, layouts inside folders, a real database behind something like notes.ts, and an action for every change. Sort these jobs: which file in the notes app does each one?
- Gives every page its
<html>and<body>. - Shows the All notes and New note links on every page.
- Gives the browser tab the title
Notes. - Shows the list of notes at
/. - Shows the box and the Save button at
/new. - Keeps the notes and adds a new one.
- Reads the text from the form's fields.
- Tells Next.js the list at
/is out of date.
Sort each job into the file of the notes app that does it.
Common mix-ups
- "The layout is part of the home page." No. It's around every page: the recorded
/newhas the same header and links as/. - "A page needs an Effect to load its data." No. A page starts as a Server Component, so it can be
asyncand wait for the data while it renders. The notes were in the first HTML. - "A form needs an API and a fetch call." No.
<form action={addNote}>calls the Server Action. Next.js makes the request, even before the page's JavaScript has loaded. - "Saving the note updates the list." No. Without
revalidatePath("/"), Next.js keeps sending the HTML of/it made during the build. - "Only my form can call my action." No. An action is a request to your server, and anyone can send one. Check who's asking inside the action.
- "What
next devshows is what visitors get." No.next devmakes pages again on every request, for writing code. Visitors get whatnext buildmade, run bynext start. - "The notes are saved for good." No. They live in the server's memory. A new
next startbegins again with the two notes in the file.
Practice exercises
These exercises are about Next.js projects, which can't run inside this page. Answer first, then use the hints.
This is app/page.tsx in step 4. Which notes are already in the HTML of / when it arrives?
import { getNotes } from "./notes";
export default async function Home() {
const notes = await getNotes();
return (
<main>
<h1>All notes</h1>
<ul>
{notes.map((note) => (
<li key={note.id}>{note.text}</li>
))}
</ul>
</main>
);
}
Buy mint tea and Call Asha. Home ran during next build and waited for getNotes(), so the HTML that the recorder got for / holds <li>Buy mint tea</li> and <li>Call Asha</li>, before any JavaScript runs.
These are the app's files in step 4. Both pages show Notes in the browser tab, but neither page file says so. Which file gives them that title?
app/
new/
page.tsx
layout.tsx
notes.ts
page.tsximport Link from "next/link";
export const metadata = { title: "Notes" };
export default function RootLayout({ children }: LayoutProps<"/">) {
return (
<html lang="en">
<body>
<nav>
<Link href="/">All notes</Link> <Link href="/new">New note</Link>
</nav>
{children}
</body>
</html>
);
}
app/layout.tsx. Line 3 exports metadata = { title: "Notes" }, and the layout is around every page, so / and /new both get <title>Notes</title>.
Under the heading, show how many notes there are. What does the recorded page show on that line?
// app/page.tsx
import { getNotes } from "./notes";
export default async function Home() {
const notes = await getNotes();
return (
<main>
<h1>All notes</h1>
{/* Show how many notes there are here, like "2 notes" */}
<ul>
{notes.map((note) => (
<li key={note.id}>{note.text}</li>
))}
</ul>
</main>
);
}import { getNotes } from "./notes";
export default async function Home() {
const notes = await getNotes();
return (
<main>
<h1>All notes</h1>
<p>{notes.length} notes</p>
<ul>
{notes.map((note) => (
<li key={note.id}>{note.text}</li>
))}
</ul>
</main>
);
}
Line 8 shows notes.length, the number of items in the array, followed by the word notes. The recorded page shows 2 notes, between the heading and the list.
With this app/actions.ts, pressing Save saves the note and goes back to /, but the list doesn't show it. Which line is missing?
"use server";
import { redirect } from "next/navigation";
import { saveNote } from "./notes";
export async function addNote(formData: FormData) {
const text = String(formData.get("text") ?? "").trim();
if (text) await saveNote(text);
redirect("/");
}
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { saveNote } from "./notes";
export async function addNote(formData: FormData) {
const text = String(formData.get("text") ?? "").trim();
if (text) await saveNote(text);
revalidatePath("/");
redirect("/");
}
revalidatePath("/"), before redirect("/"), with its import from next/cache. Without it, Next.js keeps sending the HTML of / from the build: the recording of this version shows two notes and x-nextjs-cache: HIT after Save.
With this app/actions.ts, next build stops while it makes /new: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server". Which line is missing?
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { saveNote } from "./notes";
export async function addNote(formData: FormData) {
const text = String(formData.get("text") ?? "").trim();
if (text) await saveNote(text);
revalidatePath("/");
redirect("/");
}
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { saveNote } from "./notes";
export async function addNote(formData: FormData) {
const text = String(formData.get("text") ?? "").trim();
if (text) await saveNote(text);
revalidatePath("/");
redirect("/");
}
"use server"; on line 1. Without it, addNote is an ordinary function, and the page can't hand an ordinary function to a form. The recorded build stops while making /new with: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".
A visitor presses Save without typing anything. The server answers 303 and the browser goes to /. What does the list show?
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { saveNote } from "./notes";
export async function addNote(formData: FormData) {
const text = String(formData.get("text") ?? "").trim();
if (text) await saveNote(text);
revalidatePath("/");
redirect("/");
}
The same two notes, Buy mint tea and Call Asha. "" is falsy, so line 9 skips saveNote, but lines 10 and 11 still run. The recording of this case shows 303 to /, and then / with only the two notes.
Check your understanding
For each question, think about which file does the job, and when it runs: during the build, or when a request comes in.
Question 1 of 7Which file gives every page of the notes app its
<html>and<body>?Choose an answer to see the explanation.
Question 2 of 7In step 4, when did
Homerun to make the HTML of/that the recorder got?Read the code, then predictTSXimport { getNotes } from "./notes"; export default async function Home() { const notes = await getNotes(); return ( <main> <h1>All notes</h1> <ul> {notes.map((note) => ( <li key={note.id}>{note.text}</li> ))} </ul> </main> ); }Choose an answer to see the explanation.
Question 3 of 7Before the page's JavaScript has loaded, what does pressing Save send?
Read the code, then predictTSXimport { addNote } from "../actions"; export default function NewNotePage() { return ( <main> <h1>New note</h1> <form action={addNote}> <input name="text" aria-label="Note" placeholder="Write a note" /> <button type="submit">Save</button> </form> </main> ); }Choose an answer to see the explanation.
Question 4 of 7This is
app/actions.tswithoutrevalidatePath. After savingWater the plants, what does/list?Read the code, then predictTypeScript"use server"; import { redirect } from "next/navigation"; import { saveNote } from "./notes"; export async function addNote(formData: FormData) { const text = String(formData.get("text") ?? "").trim(); if (text) await saveNote(text); redirect("/"); }Choose an answer to see the explanation.
Question 5 of 7What did the server answer to the submitted form, in the recording?
Choose an answer to see the explanation.
Question 6 of 7What does
next startdo?Choose an answer to see the explanation.
Question 7 of 7Where do the saved notes live in this app?
Choose an answer to see the explanation.
Key takeaways
app/layout.tsxis the root layout: the<html>, the<body>and whatever every page shares. Each page goes where{children}is.- A
page.tsxmakes an address:app/page.tsxis/, andapp/new/page.tsxis/new. Linkfromnext/linklinks your own pages. It arrives as a plain<a>, and Next.js makes the move faster once the page's JavaScript runs.- Pages start as Server Components, so they can be
asyncand wait for their data. The notes were in the HTML, made during the build. - A
"use server"file holds Server Actions.<form action={addNote}>calls one, even before JavaScript loads;revalidatePathmakes the changed page again, andredirectmoves the visitor on. next buildmakes the app and prints the route table;next startruns it.next devis only for writing code.
Remember the one-liner.
Pages and a layout in app make the app, Server Components read the data, Server Actions save it, and next build then next start run it.
Coming next: File-system routing. It opens the Routing stage with the rules you used in step 2: how folders become parts of an address, and which files never become pages.