BiTree
  • Search For Lessons
  • Curriculum
  • Pricing
  • For Educators
  • Become a Tutor
  • About
  • Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
BiTree

Live coding lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Search For Lessons
  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Become a Tutor
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 BiTree. All rights reserved.
Curriculum/Web Development/React and Next.js/App Router: Layouts, Pages, Loading, Errors
50 minBeginner

App Router: Layouts, Pages, Loading, Errors

After this lesson, you will be able to: Build a multi-page Next.js app using file-based routing, share UI across routes with nested layouts, handle slow data with `loading.tsx`, and gracefully recover from crashes with `error.tsx` — all without writing a single line of router config.

The App Router is what makes Next.js feel like a framework instead of a library. You don't configure routes; you create files. You don't build loading states; you create `loading.tsx`. You don't write error boundaries; you create `error.tsx`. This lesson takes you from zero understanding of Next.js routing to fluent on the four special files every page needs.

Prerequisites:Project Setup with create-next-app

File-based routing in 60 seconds

In `app/`, every folder is a URL segment. Every folder that contains a `page.tsx` becomes a real page. `app/page.tsx` → `/`. `app/about/page.tsx` → `/about`. `app/blog/[slug]/page.tsx` → `/blog/anything-here`. That's the whole routing system. No `router.config.js`, no `<Route>` components, no string-based path matching. The file system IS the routing table.

Pages and folders

Create the folders and files below. Within minutes you have a three-page app. Notice that the folder name becomes the URL.

tsx
app/
├── page.tsx // /
├── about/
│ └── page.tsx // /about
├── blog/
│ ├── page.tsx // /blog
│ └── [slug]/
│ └── page.tsx // /blog/anything
└── layout.tsx // wraps every page
// app/blog/[slug]/page.tsx
export default function BlogPost({
params,
}: {
params: Promise<{ slug: string }>;
}) {
// In Next 15+, params is a Promise — await it inside an async server component.
return <BlogPostInner params={params} />;
}
async function BlogPostInner({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return (
<article>
<h1>Post: {slug}</h1>
</article>
);
}

Layouts — shared UI across routes

A `layout.tsx` wraps every page below it in the same folder. Put your navbar, footer, and global wrappers here once instead of in every page.

html
// app/layout.tsx — the root layout (REQUIRED)
export const metadata = {
title: "My Site",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<header className="border-b p-4">
<nav className="flex gap-4">
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/blog">Blog</a>
</nav>
</header>
<main>{children}</main>
</body>
</html>
);
}
// app/blog/layout.tsx — a nested layout, ONLY applies to /blog/*
export default function BlogLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<section className="max-w-3xl mx-auto py-8">
{children}
</section>
);
}

Loading and error states

Drop a `loading.tsx` next to a `page.tsx` and Next.js shows it automatically while the page is fetching. Drop an `error.tsx` and Next.js catches any thrown error and shows the fallback. Zero router config.

html
// app/blog/loading.tsx — shown while /blog/* is rendering
export default function Loading() {
return <p className="p-8">Loading posts...</p>;
}
// app/blog/error.tsx — shown if any page in /blog/* throws
"use client"; // error boundaries MUST be client components
export default function ErrorBoundary({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div className="p-8">
<h2 className="text-xl font-semibold">Something went wrong</h2>
<p className="text-slate-600 mt-2">{error.message}</p>
<button
onClick={reset}
className="mt-4 rounded bg-slate-900 px-4 py-2 text-white"
>
Try again
</button>
</div>
);
}
// app/blog/[slug]/not-found.tsx — shown when notFound() is called
export default function NotFound() {
return <p className="p-8">No post by that slug.</p>;
}

Linking between pages

Use the `<Link>` component from `next/link`, not raw `<a>` tags, for in-app navigation. `<Link>` does client-side routing (no full page reload), prefetches the destination on hover, and integrates with Next.js's loading states. Reserve `<a href>` for links that leave your site (external URLs, file downloads).

Link component

One import, one component, no surprises.

python
import Link from "next/link";
export default function Nav() {
return (
<nav className="flex gap-4">
<Link href="/">Home</Link>
<Link href="/blog" className="font-semibold">Blog</Link>
<Link href="/blog/hello-world">Hello post</Link>
<a href="https://nextjs.org" target="_blank" rel="noopener">
Docs (external)
</a>
</nav>
);
}

Build a two-page app

In `app/`, you have a homepage at `app/page.tsx`. Add an `app/about/page.tsx` that exports a default `About()` function returning a heading and a paragraph. Then update the root layout `app/layout.tsx` to include a `<Link href="/about">About</Link>` in its `<nav>`. Required patterns check for the `About` component, the Link import, and the new nav entry.

Loading exercise…

💡 Common mistakes only experienced devs catch

Five App Router traps. (1) **Naming the file wrong** — Next.js routes only fire for `page.tsx`/`page.jsx`. Spelling it `Page.tsx` or `index.tsx` silently does nothing. (2) **Missing the root `app/layout.tsx`** — every Next.js app requires it; without it, dev server crashes at startup. (3) **Using `<a>` for internal links** — works, but forces a full page reload and skips Next.js's prefetching. Always use `<Link>` for in-app navigation. (4) **Forgetting `'use client'` on error boundaries** — `error.tsx` files MUST be client components (they need to render based on caught errors). The error message is misleading; this is the diagnosis. (5) **Mutating `params` directly** — in Next 15+, `params` is a Promise; you must `await` it. Treating it like a sync object throws confusing errors.

Sign in and purchase access to unlock this lesson.

Sign in to purchase
←Project Setup with create-next-app
Back to React and Next.js
React Hooks→