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.

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:

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.