Project structure
Find your way around a Next.js project: the app folder and its special files, public, src, next.config.ts, and where your own files can go.
- 01Read a project's foldersSay what app, public and the top-level files are for, and which files in app become addresses.
- 02Recognize the special filesName what layout, page, loading, error, not-found and route each make, ahead of the lessons that teach them.
- 03Put files in the right placeChoose between src and the top level, and keep your own components and data next to a page or beside app.
A map of the tea shop's project
Creating a Next.js app left you with a folder full of files. This lesson is the map: what each folder and file is for, which of them become addresses on the site, and where your own files can go. Here is the tea shop's project, with two pages and a menu file:
tea-shop/
app/
teas/
page.tsx
tea-list.tsx
teas.ts
layout.tsx
page.tsx
public/
menu.txt
next.config.ts
package.json
tsconfig.jsonLines 2 to 8 are the app folder, with the pages. Lines 9 and 10 are the public folder, with one text file. Lines 11 to 13 are settings files, at the project's top level: directly in tea-shop, not inside another folder. That's a real project. It was built with next build, run with next start, and asked for five addresses. Click the files to read them, and the addresses above the page to switch between them.
app/teas/page.tsximport { getTeas } from "./teas";import TeaList from "./tea-list"; export default async function TeasPage() { const teas = await getTeas(); return ( <main> <h1>Our teas</h1> <TeaList teas={teas} /> </main> );}Three teas, brewed fresh.
The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.
app/teas/page.tsx is the page for /teas. Lines 1 and 2 import two files that sit next to it in the same folder: teas.ts, the shop's data, and tea-list.tsx, a component that draws the list. The Browser tab shows the page at /teas with its three teas. Now pick /teas/tea-list or /teas/teas: both answer 404, the page for an address that doesn't exist. A file in app only becomes an address when it's a page.
app holds the files that become addresses, public holds files sent exactly as they are, and the files beside them hold the settings.Each row is one thing at the top level of the project:
| File or folder | What it's for | Who reads it |
|---|---|---|
app/ | Your pages, layouts and the special files, in folders that become addresses. | Next.js, when it builds and serves the app. |
public/ | Files sent exactly as they are, like pictures and a menu. | The server, which answers with them by name. |
next.config.ts | Next.js's settings. | Next.js, every time next dev, next build or next start starts. |
package.json | The packages the project needs, and its scripts. | npm. |
tsconfig.json | TypeScript's settings, including the @/* alias. | TypeScript, your editor, and Next.js for the alias. |
Customers walk into the rooms that have a door to the street, and each door has a sign. The back room is right behind the counter, but it has no door to the street, so customers never walk into it, even though the tea brewed there is served at the counter. The leaflets by the entrance are there for anyone to take. And the office holds the rules the whole building runs by.
- In real life: Rooms with a door to the street, each with its own sign
- In Next.js: Folders in
appwith apage.tsx, each an address - In real life: The back room behind the counter, with the stock and the tools
- In Next.js: Files next to a page, like
tea-list.tsx: no address - In real life: The rack of free leaflets by the door
- In Next.js:
public: anyone who asks gets the file, as it is - In real life: The office, with the building's rules
- In Next.js:
next.config.tsand the other settings files
In a Next.js project, app holds the pages and the files that become addresses, public holds files sent as they are, and the files at the top level hold the settings. Inside app, only special file names, like page.tsx, make an address, so your own files can live right next to the pages that use them.
The app folder: folders become addresses
The app folder is the App Router: the part of Next.js that turns folders into addresses. A route is one address the site answers, like /teas. The rules, as a first look:
appitself is the address/, soapp/page.tsxis the home page.- Each folder inside adds a part to the address, called a segment:
app/teasis/teas. - A segment becomes a page only when its folder has a
page.tsx. That file's component is what visitors see. app/layout.tsxis the root layout: it's around every page. In the recording, every response, even the 404, starts with the layout's links,HomeandTeas.
Open the example's Terminal tab. After building, Next.js lists the routes it found: ○ /, ○ /_not-found and ○ /teas. /_not-found is the page Next.js adds for addresses that don't exist. tea-list.tsx and teas.ts aren't in the list.
app is a part of an address, and a page.tsx makes it a page. The files next to a page have no address: the recording asked for /teas/tea-list and /teas/teas, and both answered 404.Dynamic addresses like /teas/mint, nested layouts, and the folders that don't change the address all build on these rules. The Routing and navigation stage teaches them one by one.
Special files: a first look
page.tsx and layout.tsx are two of Next.js's special files: names that mean something in app. Each one gives its folder a job. The file can end in .tsx, .jsx or .js (and route in .ts or .js). Each row is one of them, with the lesson that teaches it:
| File | What it makes | Taught in |
|---|---|---|
layout.tsx | The part around the pages in its folder and below, like the shop's links. | Layouts and pages |
page.tsx | The page for its folder's address. | File-system routing |
loading.tsx | What shows while a page in its folder is still waiting for something. | Loading UI |
error.tsx | What shows in place of the page if it throws an error. | Error handling |
not-found.tsx | What shows for an address that doesn't exist. | Not found, forbidden and unauthorized |
route.ts | An address that answers with data, like JSON, instead of a page. | Route Handlers |
Here is the tea shop with four more of them added to app. The file shown first is app/not-found.tsx:
app/not-found.tsximport Link from "next/link"; export default function NotFound() { return ( <main> <h1>Not on the menu</h1> <p> <Link href="/teas">See every tea</Link> </p> </main> );}See every tea
The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.
not-found.tsx: the Browser tab opens on/oolong, an address the shop doesn't have. It answered with status 404, and with this file's page: line 6 is the headingNot on the menu, inside the same layout as every page.route.ts:app/api/teas/route.tsmakes the address/api/teas, which answers with data instead of a page. Pick it, then open the Network tab: the body is the three teas as JSON, withcontent-type: application/json. The Terminal tab lists it as a route,○ /api/teas, next to the pages.error.tsx: shown in place of a page that throws an error. Nothing here throws, so it never shows. Error files must be Client Components, so line 1 is"use client", and its button callsretryto try the page again.loading.tsx: shown while a page waits for something, like data that's made for each request. Every page here is made during the build, soBrewing…never shows either.
The special files in one folder fit inside each other, like boxes. Next.js puts the page inside loading.tsx, that inside error.tsx, and all of it inside the layout:
error.tsx, then loading.tsx, and the page in the middle. (Simplified: template.tsx and not-found.tsx have their places in this order too.) Later lessons open each one.Because these names mean something, don't give your own files the same names. A component called page.tsx in a folder would turn that folder into an address. Name it for what it is, like tea-list.tsx.
public: files served as they are
Every file in public gets an address: its path inside public, starting at the top of the site. The word public isn't part of it. So public/menu.txt is /menu.txt, and a picture at public/images/green-tea.svg is /images/green-tea.svg.
In the first example, pick /menu.txt and open the Network tab. The server sent the file exactly as it is, as text/plain, with Cache-Control: public, max-age=0. That header lets browsers keep a copy, but tells them to ask the server again every time before they use it: Next.js can't know whether a file in public has changed. A page uses these files by their address, like <img src="/images/green-tea.svg" />.
Three things to know:
- Anyone can download anything in
public. Never put secrets there. - Files in
publicaren't built: no TypeScript, no bundling. They're sent byte for byte. - The site's icon and files for search engines, like
favicon.icoandrobots.txt, have special files of their own inapp. That's whycreate-next-appputfavicon.icoinapp, not inpublic.
The optional src folder
Some teams like their own code apart from the settings files. Next.js lets you move app into a folder named src, short for source. It's the --src-dir answer in create-next-app. The app works the same; only the folder moves. The settings stay at the top level, and so does public. One line of tsconfig.json changes, so the @/ alias points into src:
"paths": {
"@/*": ["./src/*"]
}The middle line maps @/* to ./src/*, so import TeaList from "@/app/teas/tea-list" finds src/app/teas/tea-list.tsx. Without src, it says ./*. Try the four layouts below. Each one was built and run with real Next.js. Before you click, guess what / and /menu.txt answer for each.
app/page.tsxexport default function Home() { return ( <main> <h1>Tea shop</h1> <p>This page is app/page.tsx.</p> </main> );}This page is app/page.tsx.
The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.
The usual layout: app and public side by side at the top. / shows the page from app/page.tsx, and /menu.txt is the file in public.
So, with src:
appmoves intosrc, and so do your own folders, likecomponentsandlib.public,next.config.ts,package.json,tsconfig.jsonand the.envfiles stay at the top level.- Keep only one
app. If there's one at the top level too, Next.js uses it and ignoressrc/app.
next.config.ts and the other top-level files
next.config.ts holds Next.js's settings. Next.js reads it every time next dev, next build or next start starts: a build of the default project prints ✓ Running next.config.ts took 63ms. Every recorded example in this course uses the one create-next-app writes without Tailwind, which turns on Cache Components and Partial Prefetching. Add a setting, and Next.js behaves differently. Here the shop gains one:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
cacheComponents: true,
partialPrefetching: true,
poweredByHeader: false,
};
export default nextConfig;
Line 3 makes the settings object, and its type, NextConfig, lets your editor suggest every setting. Lines 5 and 6 are the ones create-next-app wrote. Line 7 is new: poweredByHeader: false. Line 10 exports the object for Next.js. Compare the headers with and without it:
next.config.tsNext.jsnext.config.tsimport type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ cacheComponents: true, partialPrefetching: true,}; export default nextConfig;GET /200
- Response headers
Varyrsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch, Accept-Encodingx-nextjs-cacheHITx-nextjs-prerender1x-nextjs-stale-time300X-Powered-ByNext.jsCache-Controls-maxage=31536000Content-Typetext/html; charset=utf-8
The HTML that arrived, one element per line, with the code of its 15 scripts left out
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preload" as="script" fetchpriority="low" href="/_next/static/chunks/1x6obpmlu1_c2.js">
<script src="/_next/static/chunks/06bs-nf19b2js.js" async=""></script>
<script src="/_next/static/chunks/2ki52vdnbo2ft.js" async=""></script>
<script src="/_next/static/chunks/3l_qmp-61z4yc.js" async=""></script>
<script src="/_next/static/chunks/turbopack-3-ry682tkyiqk.js" async=""></script>
<script src="/_next/static/chunks/3oh00mkbkq8u6.js" async=""></script>
<title>Tea shop</title>
<script src="/_next/static/chunks/0cz1d0mv5g_q7.js" nomodule=""></script>
</head>
<body>
<div hidden="">
<!--$-->
<!--/$-->
</div>
<nav><a href="/">Home</a> <a href="/teas">Teas</a></nav>
<!--&-->
<main>
<h1>Tea shop</h1>
<p>Three teas, brewed fresh.</p>
</main>
<!--$-->
<!--/$-->
<!--/&-->
<script id="_R_">…</script>
<script src="/_next/static/chunks/1x6obpmlu1_c2.js" async=""></script>
<script>…</script>
<script>…</script>
<script>…</script>
<script>…</script>
<script>…</script>
<script>…</script>
<script>…</script>
</body>
</html>Cache Components and Partial Prefetching are on. Among the response's headers is X-Powered-By: Next.js, which tells everyone what the site is built with.
Most settings are for later lessons: redirects, images, and the bundler. A few other files at the top level have a meaning to Next.js too, and each has its lesson later:
.env,.env.localand others: environment variables, settings like secret keys that your code reads, kept out of git.proxy.ts: code that runs before a request reaches a page, to redirect it or check who's signed in. Before Next.js 16 it was calledmiddleware.ts.instrumentation.ts: code that runs once when the server starts, for tools that watch the app.
Where your own files go
Your components, data and helpers aren't special files, and Next.js doesn't mind where they go. Keeping a file next to the code that uses it is called colocation. In app, a file next to a page gets no address: only page.tsx and route.ts make addresses. That's why app/teas/tea-list.tsx answered 404.
Having no address isn't the same as staying on the server, though. What a page renders is sent to the browser in its HTML: the recorded /teas holds every tea from teas.ts. And a file that a Client Component imports is sent to the browser as code, wherever it sits (Server Components explains the two kinds). Secrets, like an API key, belong in code that only runs on the server and in environment variables the browser never gets; the Server-only code and environment variables lesson shows how. Each row is one place your files can go:
| Where | What goes there | Does it get an address? |
|---|---|---|
Next to the page that uses it, in app | A component or data only one page uses, like app/teas/tea-list.tsx. | No. Only page.tsx and route.ts make addresses. |
In a folder beside app | Code many pages share, like components/ and lib/, imported with @/. | No. Next.js only makes addresses from app. |
In a private folder, like app/_drafts | Files you want to keep out of routing, even a page.tsx. | No. The underscore keeps the whole folder out. |
Many teams keep shared code beside app, in folders like components and lib. The names mean nothing to Next.js. Here the shop does that, and imports with the @/ alias instead of ../:
import { getTeas } from "@/lib/teas";
import TeaList from "@/components/tea-list";
export default async function TeasPage() {
const teas = await getTeas();
return (
<main>
<h1>Our teas</h1>
<TeaList teas={teas} />
</main>
);
}
Line 1 imports getTeas from lib/teas.ts, and line 2 imports TeaList from components/tea-list.tsx. @/ means the project's top level, from tsconfig.json. The recording below proves both halves: /teas shows the teas, and /components/tea-list and /lib/teas answer 404.
app/teas/page.tsximport { getTeas } from "@/lib/teas";import TeaList from "@/components/tea-list"; export default async function TeasPage() { const teas = await getTeas(); return ( <main> <h1>Our teas</h1> <TeaList teas={teas} /> </main> );}- Green tea: $4
- Mint tea: $3
- Lemon tea: $3
The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.
One more tool, as a first look: a folder whose name starts with an underscore, like _drafts, is a private folder. Next.js leaves it out of routing completely, even a page.tsx inside it:
app/_drafts/page.tsxexport default function Draft() { return ( <main> <h1>Autumn menu (draft)</h1> </main> );}The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.
The Terminal tab lists /, /_not-found and /teas, and no /_drafts. Asked for it, the server answered 404. Private folders and route groups get a lesson of their own: Route groups and private folders. Whichever way you choose, use it across the whole project, so everyone knows where to look.
Where does this file go?
The shop keeps growing: an About page, a logo, a menu to download, secret keys. Each card is a file it needs. Sort it into the place it belongs:
- The page for
/about. - A
TeaCardcomponent only the/teaspage uses. logo.svg, which every page shows with<img src="/logo.svg">.- A menu people download at
/menu.pdf. - The setting that turns off the
X-Powered-Byheader. .env.local, with the shop's secret keys.- The file that sets up the
@/*alias.
Sort each file into app, public, or the project's top level.
One way to decide: if visitors open it as a page, it's in app. If they download it as it is, it's in public. If a tool reads it, it's at the top level. The rest is your code, and it goes next to whatever uses it.
Common mix-ups
- "Every file in
appbecomes a page." No. Onlypage.tsxmakes a page, androute.tsan address for data./teas/tea-listanswered 404. - "Files in
publicare at/public/…." No.public/menu.txtis at/menu.txt. - "With
src, everything moves into it." No.publicand the settings files stay at the top level. Moved,publicstops working. - "Next.js warns if there are two
appfolders." No. It quietly uses the top-level one and ignoressrc/app. - "A file with no address never reaches the browser." No. What a page renders is in its HTML, and a file a Client Component imports is sent as code. Keep secrets in server-only code and environment variables.
- "Components must go in a
componentsfolder." No. The name means nothing to Next.js. Next to the page, or besideapp, both work. - "
next.config.tsis where pages are listed." No. Pages come from the folders inapp.next.config.tsonly holds settings.
Practice exercises
Answer first, then use the hints.
Which addresses does next build list for this app folder, besides /_not-found?
app/
about/
page.tsx
lib/
format.ts
teas/
page.tsx
price.ts
layout.tsx
page.tsx/, /about and /teas. price.ts sits next to a page, and lib has no page.tsx, so neither gets an address, like tea-list.tsx and teas.ts in the recorded shop.
A picture is saved at public/images/green-tea.svg. What address does a page put in its src?
/images/green-tea.svg. public itself is the top of the site, like public/menu.txt at /menu.txt in the recording.
This is app/teas/page.tsx, next to app/teas/tea-list.tsx. What does the server answer for /teas/tea-list?
import { getTeas } from "./teas";
import TeaList from "./tea-list";
export default async function TeasPage() {
const teas = await getTeas();
return (
<main>
<h1>Our teas</h1>
<TeaList teas={teas} />
</main>
);
}
404. tea-list.tsx is an ordinary component next to the page. The recorded server answered /teas/tea-list with status 404 and the not-found page.
You moved app into src. Rewrite the alias line in tsconfig.json.
"paths": {
"@/*": ["./*"]
}"paths": {
"@/*": ["./src/*"]
}With the code in src, @/app/teas/tea-list has to find src/app/teas/tea-list.tsx. That's the line create-next-app --src-dir writes, and the src playground's projects use.
A teammate added src/app but forgot to delete app. Which file does / show?
app/
layout.tsx
page.tsx
src/
app/
layout.tsx
page.tsxapp/page.tsx. With an app folder at the top level, Next.js ignores src/app. The recording shows This page is app/page.tsx. and the build printed no warning.
Every response has the header X-Powered-By: Next.js. Which line in next.config.ts removes it?
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
cacheComponents: true,
partialPrefetching: true,
};
export default nextConfig;
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
cacheComponents: true,
partialPrefetching: true,
poweredByHeader: false,
};
export default nextConfig;
poweredByHeader: false on line 7. The recorded response for / has no X-Powered-By header any more; everything else is the same.
Check your understanding
For each question, ask which folder a file is in, and whether its name means something to Next.js.
Question 1 of 7Which file makes
/teasa page in the tea shop?Choose an answer to see the explanation.
Question 2 of 7The tea shop has
public/menu.txt. At which address does the server send it?Choose an answer to see the explanation.
Question 3 of 7A project has both
app/page.tsxandsrc/app/page.tsx. Which one does/show?Choose an answer to see the explanation.
Question 4 of 7You move your code into
src. Where doespublicgo?Choose an answer to see the explanation.
Question 5 of 7What does this
next.config.tschange?Read the code, then predictTypeScriptimport type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ cacheComponents: true, partialPrefetching: true, poweredByHeader: false, }; export default nextConfig;Choose an answer to see the explanation.
Question 6 of 7Which special file decides what an address that doesn't exist shows?
Choose an answer to see the explanation.
Question 7 of 7A page sits at
app/_drafts/page.tsx. What does/_draftsanswer?Choose an answer to see the explanation.
Key takeaways
appholds the pages;publicholds files sent as they are; the top level holds the settings files.- In
app, each folder is a segment of the address, andpage.tsxmakes it a page. Other files next to it have no address. - Special files give a folder a job:
layout,page,loading,error,not-foundandroute. They fit inside each other, with the page in the middle. public/menu.txtis at/menu.txt. Nothing secret goes inpublic.srcis optional:appmoves into it;publicand the settings stay at the top level. Keep only oneapp.- Your own files can go next to the page that uses them, in folders beside
app, or in a private_folder.
Remember the one-liner.
Pages in app, files as they are in public, settings at the top; only special names make addresses.
Coming next: Your first Next.js app. It builds a small notes app from scratch, with two pages, a shared layout, a link, data from the server, and a form that saves.