Unit 1 · Module 5
How Next.js Reads Your Folders
File-based routing, page.tsx, and layout.tsx
The folder is the URL
In Next.js, your folder structure is your URL structure. That's it. That's the whole idea behind file-based routing.
Here's PetKarma's app/ folder:
app/
app/
├── (app)/
├── (public)/
├── api/
├── error.tsx
├── globals.css
├── layout.tsx
├── loading.tsx
├── not-found.tsx
└── page.tsx And inside (app)/:
app/(app)/
(app)/
├── dashboard/
├── friend-groups/
├── friend-profile/
├── history/
├── my-pets/
├── profile/
├── schedule/
├── settings/
├── layout.tsx
└── loading.tsx
Those folder names — dashboard, my-pets, schedule —
become the routes: /dashboard, /my-pets, /schedule. You
don't configure anything. You just make a folder, drop a file inside it, and Next.js figures
out the rest.
What makes a folder into a route: page.tsx
A folder alone isn't a route. Next.js needs a specific file inside it to know the folder should
be rendered to the browser. That file is always called page.tsx.
Contents don't determine the route. The filename does.
So app/my-pets/page.tsx becomes /my-pets.
app/dashboard/page.tsx becomes /dashboard. And
app/page.tsx — the one sitting at the root of app/ — becomes your
homepage at /.
layout.tsx is the real wrapper
layout.tsx is one of the most important files in a Next.js app and beginners
almost always misunderstand it at first.
Every page in your app needs certain things wrapped around it. Navigation. Footer. Providers. Analytics. You don't want to repeat all of that on every page.
layout.tsx is the wrapper. Everything inside it renders on every
page within its scope. Your pages slot into it like a picture into a frame. The frame stays the
same, the picture changes.
Here's Opsette's root layout.tsx:
app/layout.tsx
import type { Metadata } from 'next'
import { Providers } from '@/components/providers/Providers'
import GoogleAnalytics from './GoogleAnalytics'
import './globals.css'
export const metadata: Metadata = {
title: 'Opsette',
description: 'All-in-one business operations platform...'
}
This file is plain on purpose. The real power isn't in what's written inside it — it's in what
gets slotted into it. Somewhere in there is a line that looks like {children}.
That's the placeholder where every page in your app gets inserted.
Why a wrapper imports anything at all
A layout imports the things that every page needs to have available. Providers. Analytics. Global styles. By importing them at the layout level, they automatically apply to every page inside — you don't have to remember to add them to each one.
Analogy: The power of the wrapper
You don't pass Google Analytics to every page individually. You put it in the wrapper once and every child gets it automatically. That's the whole power of layout.
That's also why you'll see multiple layout files in a real app — one at the root of
app/ for global concerns, and separate ones inside section folders like
(dashboard) or (auth) for section-specific wrapping. Different
frames for different sections.
Why a page imports things too
A page that imports nothing shows a blank screen. The route exists, but there's nothing to display.
Here's Opsette's homepage, app/page.tsx:
app/page.tsx
'use client'
import MarketingLayout from '@/components/layout/MarketingLayout'
import LandingHero from '@/components/landing/LandingHero'
import LandingShowcase from '@/components/landing/LandingShowcase'
import LandingCTA from '@/components/landing/LandingCTA'
const LandingPage = () => {
return (
<MarketingLayout>
<LandingHero />
<LandingShowcase />
<LandingCTA />
</MarketingLayout>
)
}
The page is the composer. It imports the pieces and assembles them into what
the user actually sees. The routing itself happens because the file is named
page.tsx and sits where it sits. What's inside is how the page looks when someone
visits.
layout.tsx vs page.tsx — neighbors, not equals
Seeing both files in the same folder makes it feel like they're at the same level of importance. They're not.
layout.tsxis the wrapper. It renders on every page within its scope.page.tsxis just one page — the one for that specific folder's URL.
They happen to live next to each other because Next.js looks for both in the same place. That's it. Proximity isn't hierarchy.
Analogy: Frame and picture
The layout is the frame. The page is the picture. Every picture slots into the frame.
The special filenames
Next.js recognizes a handful of filenames and gives each one a specific job:
page.tsx— makes the folder a routelayout.tsx— wraps every page in that folder (and its subfolders)loading.tsx— shown while the page is loadingerror.tsx— shown when something crashesnot-found.tsx— shown when a route doesn't exist
You don't have to include these in every folder. Next.js falls back to the nearest parent if a folder doesn't define its own. But when you see them, you know exactly what they do without reading a line of code inside.