Welcome to Next.js
In this lesson we explore the App Router paradigm introduced in Next.js 13 and refined through versions 14 and 15. You'll learn why Vercel made this shift and what it means for your day-to-day development.
Key Concepts
- The
app/directory vs the oldpages/directory - Layouts, Pages, and Templates
- The role of React Server Components (RSC)
By the end of this lesson you will feel comfortable navigating a fresh Next.js project and understanding where each file type belongs.
Introduction to the App Router
If you've used Next.js before version 13, you built everything inside a pages/ folder.
Each file became a route, data fetching happened through getServerSideProps or
getStaticProps, and layouts were stitched together manually using _app.tsx.
It worked — but it had real limitations as apps grew larger.
In Next.js 13, Vercel introduced a completely new way of building apps: the App Router.
It lives inside an app/ folder alongside your existing pages/ folder (if you have one),
and it's built on top of the latest React features — most importantly React Server Components.
By Next.js 15, the App Router is the default and recommended approach for all new projects.
In this lesson you'll understand why this shift happened, what's actually different under the hood, and how to navigate a freshly created Next.js project with confidence.
1. What Was Wrong With the Pages Router?
The Pages Router was great for its time, but a few pain points kept coming up:
-
Layouts were awkward. If you wanted a sidebar to persist across a set of pages,
you had to wrap every page manually or do gymnastics inside
_app.tsx. Nested layouts — like a dashboard layout inside an admin layout — were especially painful. -
Data fetching was tied to the page.
getServerSidePropsonly ran at the top-level page component. If a deeply nested component needed server data, you had to fetch it at the top and drill it down as props. That made components hard to move around. - Every component shipped JavaScript to the browser. Even components that just rendered static HTML — a blog post, a product description, a footer — sent their JS to the client, where it was hydrated. This added weight to every page.
The App Router solves all three of these problems.
2. The App Router's Core Ideas
2a. The app/ Directory
All App Router code lives inside the app/ folder at the root of your project. Here's
what a minimal Next.js 15 project looks like:
my-app/
├── app/
│ ├── layout.tsx ← Root layout (required)
│ ├── page.tsx ← Homepage "/"
│ ├── about/
│ │ └── page.tsx ← "/about"
│ └── blog/
│ ├── layout.tsx ← Layout for all /blog/* pages
│ └── [slug]/
│ └── page.tsx ← "/blog/my-post-title"
├── public/
├── next.config.ts
└── package.json
The folder structure is your URL structure. Want a route at /about? Create
app/about/page.tsx. Want /blog/any-post-title? Create
app/blog/[slug]/page.tsx. No extra configuration needed.
2b. Special File Names
Inside any folder in app/, Next.js looks for files with specific names. Each one has
a distinct job:
| File | What it does |
|---|---|
page.tsx |
The UI for this route. Makes the route publicly accessible. |
layout.tsx |
A wrapper that persists across all child routes without re-rendering. |
loading.tsx |
A skeleton/spinner shown instantly while the page loads. |
error.tsx |
A fallback UI shown when an error is thrown inside this route. |
not-found.tsx |
Shown when notFound() is called or the route doesn't exist. |
route.ts |
An API endpoint (like a REST handler). No UI. |
You don't need all of these in every folder — only create the ones your route actually needs.
2c. React Server Components — The Big Shift
This is the most important concept in the App Router. In Next.js 15, every component
inside app/ is a React Server Component (RSC) by default.
What does that mean in practice?
- The component runs only on the server. It has access to your database, file system, and environment variables directly.
- Zero JavaScript is sent to the browser for that component. The server renders it to HTML and ships the HTML. Your bundle stays small.
- You can use
async/awaitdirectly inside the component to fetch data — nouseEffect, nouseState, no loading spinner boilerplate.
Here's a simple Server Component that fetches and displays a list of posts:
// app/blog/page.tsx
// No "use client" at the top = Server Component by default
export default async function BlogPage() {
// This fetch runs on the server. The browser never sees this code.
const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5');
const posts = await res.json();
return (
<main>
<h1>Blog</h1>
<ul>
{posts.map((post: { id: number; title: string }) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
);
}
That's it. No getServerSideProps. No API route in the middle. The component itself
is async, it fetches, and it renders — all on the server.
2d. Client Components
Server Components can't use browser APIs, event handlers (onClick), or React hooks
like useState and useEffect. When you need those things, you opt a
component into the client by adding "use client" as the very first
line of the file:
// components/Counter.tsx
"use client"; // ← This line makes it a Client Component
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Client Components work exactly like React components always have — they just live inside a Next.js App Router project. The key insight is that you now choose which components go to the browser instead of everything going by default.
3. Nested Layouts — One of the Best Features
Remember how layouts were painful in the Pages Router? The App Router makes them trivial.
Each folder can have its own layout.tsx that wraps all routes inside that folder.
Layouts nest automatically.
Let's say you're building a dashboard. Here's the folder structure:
app/
├── layout.tsx ← Wraps everything (nav, footer)
├── page.tsx ← Homepage
└── dashboard/
├── layout.tsx ← Wraps all /dashboard/* routes (sidebar)
├── page.tsx ← /dashboard
└── settings/
└── page.tsx ← /dashboard/settings
And here's what those layout files look like:
// app/layout.tsx — Root layout
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<nav>My App Nav</nav>
{children} {/* page content goes here */}
</body>
</html>
);
}
// app/dashboard/layout.tsx — Dashboard layout
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div style={{ display: "flex" }}>
<aside>Sidebar</aside>
<main>{children}</main>
</div>
);
}
When a user visits /dashboard/settings, Next.js renders:
RootLayout → DashboardLayout → SettingsPage — all nested automatically.
The nav and sidebar persist across navigations without re-rendering or flashing.
4. Creating Your First Next.js 15 App
Run this command in your terminal to scaffold a new project:
npx create-next-app@latest my-app
You'll be asked a few questions. Here's what to pick for this course:
✔ Would you like to use TypeScript? → Yes
✔ Would you like to use ESLint? → Yes
✔ Would you like to use Tailwind CSS? → Yes
✔ Would you like your code inside a src/ directory? → No
✔ Would you like to use App Router? → Yes ← Important!
✔ Would you like to use Turbopack for next dev? → Yes
✔ Would you like to customize the import alias? → No
Once it finishes, open the project:
cd my-app
npm run dev
Visit http://localhost:3000 in your browser. You'll see the default Next.js welcome
page. Now open the project in your code editor and look at app/page.tsx — that's
the file rendering what you see.
Delete everything inside it and replace it with this to make sure things are working:
// app/page.tsx
export default function HomePage() {
return (
<main>
<h1>Hello, Next.js 15!</h1>
</main>
);
}
Save the file. The browser updates instantly thanks to Turbopack's fast hot reload.
5. Understanding the Generated File Structure
After scaffolding, your project looks like this. Let's go through each important part:
my-app/
├── app/
│ ├── favicon.ico ← Browser tab icon
│ ├── globals.css ← Global CSS (Tailwind imports live here)
│ ├── layout.tsx ← Root layout — wraps every page
│ └── page.tsx ← The homepage "/"
├── public/ ← Static files (images, fonts, etc.)
├── next.config.ts ← Next.js configuration
├── tailwind.config.ts ← Tailwind configuration
├── tsconfig.json ← TypeScript configuration
└── package.json
Notice there's no pages/ folder. This is a pure App Router project.
The public/ folder is served at the root — so
public/logo.png is accessible at http://localhost:3000/logo.png.
6. Common Gotchas for Beginners
-
Don't confuse
page.tsxwithlayout.tsx. Apage.tsxis the actual content of the route. Alayout.tsxis the wrapper around it. A folder without apage.tsxis not a public route. -
"use client"is contagious downward, not upward. If a parent component is a Server Component, its children can be either. But if a component is a Client Component, all components it imports that aren't separately marked will also be treated as Client Components. -
The root
layout.tsxmust have<html>and<body>tags. Next.js will throw an error if these are missing fromapp/layout.tsx. -
You can still use
pages/alongsideapp/. They coexist during migration. Just don't put the same route in both — it causes conflicts.
Key Takeaways
- The App Router replaces the Pages Router and lives in the
app/directory. - Every component is a Server Component by default — add
"use client"only when you need interactivity. - Special files like
page.tsx,layout.tsx,loading.tsx, anderror.tsxeach have a specific role. - Nested layouts are automatic — folder structure defines nesting.
- Data fetching happens directly inside async Server Components — no more
getServerSideProps.
In the next lesson, we'll go deep on file-based routing — dynamic segments, catch-all routes, route groups, and more.