Skip to content

Special Files in App Router

Think of your app/ folder like a shopping mall. Each floor (folder) has:

  • A floor plan (layout.tsx) — the walls, escalators, bathrooms (shared structure)
  • The stores (page.tsx) — the actual shops you visit
  • A “coming soon” sign (loading.tsx) — shown while a store is being built
  • A “closed” sign (error.tsx) — shown if something went wrong
  • A lost & found (not-found.tsx) — shown when someone can’t find a store

Every special file in the App Router has a unique purpose. Here’s how they nest:

flowchart TB
RootLayout["📄 layout.tsx\nRoot Layout\nWraps everything"] --> RootPage["📄 page.tsx\nHome Page\n/"]
RootLayout --> BlogLayout["📄 layout.tsx\nBlog Layout\nWraps /blog/*"]
RootLayout --> ShopLayout["📄 layout.tsx\nShop Layout\nWraps /shop/*"]
BlogLayout --> BlogLoading["⏳ loading.tsx\nShows while\n/blog loads"]
BlogLayout --> BlogPage["📄 page.tsx\nBlog Index\n/blog"]
BlogLayout --> SlugPage["📄 page.tsx\nBlog Post\n/blog/[slug]"]
BlogLayout --> BlogError["⚠️ error.tsx\nCatches errors\nin /blog/*"]
BlogLayout --> BlogNotFound["🔍 not-found.tsx\nCustom 404\nfor /blog/*"]
ShopLayout --> ShopLoading["⏳ loading.tsx\nShows while\n/shop loads"]
ShopLayout --> ShopPage["📄 page.tsx\nShop Page\n/shop"]
ShopLayout --> ShopError["⚠️ error.tsx\nCatches errors\nin /shop/*"]
RootLayout --> RootNotFound["🔍 not-found.tsx\nGlobal 404"]
style RootLayout fill:#7c3aed,color:#fff
style RootPage fill:#4f46e5,color:#fff
style BlogLayout fill:#059669,color:#fff
style ShopLayout fill:#059669,color:#fff
style BlogLoading fill:#f59e0b,color:#000
style ShopLoading fill:#f59e0b,color:#000
style BlogError fill:#dc2626,color:#fff
style ShopError fill:#dc2626,color:#fff
style BlogNotFound fill:#6366f1,color:#fff
style RootNotFound fill:#6366f1,color:#fff
style BlogPage fill:#4f46e5,color:#fff
style SlugPage fill:#4f46e5,color:#fff
style ShopPage fill:#4f46e5,color:#fff

The page is what users actually see at a route. Every route MUST have a page.tsx (or route.ts for APIs).

app/about/page.tsx
export default function AboutPage() {
return <h1>About Us</h1>;
}
// → Renders at /about

A layout wraps its child pages and persists across navigations (it doesn’t re-render when the user navigates between pages inside it).

app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="dashboard-layout">
<Sidebar /> {/* Stays mounted during navigation */}
<main>{children}</main> {/* The page changes here */}
</div>
);
}

Key: Layouts are nested — a blog layout wraps blog pages, a root layout wraps everything.

Shown instantly while the page content is being fetched. Uses React Suspense under the hood.

app/dashboard/loading.tsx
export default function DashboardLoading() {
return (
<div className="animate-pulse">
<div className="h-8 bg-gray-200 rounded w-1/4 mb-4" />
<div className="grid grid-cols-3 gap-4">
<div className="h-24 bg-gray-200 rounded" />
<div className="h-24 bg-gray-200 rounded" />
<div className="h-24 bg-gray-200 rounded" />
</div>
</div>
);
}

Catches unexpected errors in the route segment and shows a fallback UI. Must be a Client Component (has "use client").

app/dashboard/error.tsx
"use client";
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div className="error-state">
<h2>Something went wrong!</h2>
<p>{error.message}</p>
<button onClick={reset}>Try Again</button>
</div>
);
}

Shown when a route is not found (either no matching route, or notFound() was called).

// app/not-found.tsx — Global 404
import Link from "next/link";
export default function NotFound() {
return (
<div>
<h2>Page Not Found</h2>
<p>Could not find the requested page.</p>
<Link href="/">Go Home</Link>
</div>
);
}

Creates an API endpoint (not a page). Used for GET/POST/PUT/DELETE handlers.

app/api/hello/route.ts
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json({ message: "Hello World" });
}

Like layout.tsx, but re-renders on every navigation (doesn’t persist state).

// app/(marketing)/template.tsx
export default function MarketingTemplate({ children }: { children: React.ReactNode }) {
return (
<div className="page-transition">
{children}
</div>
);
// Re-renders every time user navigates — good for page transitions
}

8. default.tsx — Parallel Routes Fallback

Section titled “8. default.tsx — Parallel Routes Fallback”

Used with parallel routes (@slot). Shows content when no matching page exists for a slot.

// app/@analytics/default.tsx
export default function DefaultAnalytics() {
return <div>No analytics data available</div>;
}

When Next.js renders a route like /dashboard/settings, it nests the files from top to bottom:

layout.tsx (root) ← Mounted first
layout.tsx (dashboard) ← Wraps dashboard section
loading.tsx ← Shown while page loads
error.tsx ← Catches errors
page.tsx ← The actual page UI
flowchart LR
Request["🚀 Request\n/dashboard/settings"] --> RootL["📄 Root\nlayout.tsx"]
RootL --> DashL["📄 Dashboard\nlayout.tsx"]
DashL --> Loading["⏳ loading.tsx\n(skips if instant)"]
Loading --> Error["⚠️ error.tsx\n(if something breaks)"]
Error --> Page["📄 page.tsx\nSettings Page"]
style Request fill:#7c3aed,color:#fff
style RootL fill:#059669,color:#fff
style DashL fill:#059669,color:#fff
style Loading fill:#f59e0b,color:#000
style Error fill:#dc2626,color:#fff
style Page fill:#4f46e5,color:#fff

FilePurposeRe-renders on nav?Persists state?
page.tsxPage content✅ Yes❌ No
layout.tsxShared wrapper❌ No✅ Yes
template.tsxFresh wrapper✅ Yes❌ No
loading.tsxLoading skeletonN/AN/A
error.tsxError fallbackN/AN/A
not-found.tsx404 pageN/AN/A
route.tsAPI endpointN/AN/A
default.tsxParallel route fallback✅ Yes❌ No

  • page.tsx = what users see at a URL (required for every route)
  • layout.tsx = shared wrapper that stays mounted during navigation (sidebar, nav)
  • loading.tsx = instant skeleton shown while data loads (uses Suspense)
  • error.tsx = catches errors, shows a retry button (must be a Client Component)
  • not-found.tsx = custom 404 page for missing routes
  • route.ts = makes an API endpoint instead of a page
  • Files nest — root layout wraps child layout, which wraps page, with loading/error in between