404 Not Found

404 Not Found


nginx

App Router File-System Routing

File system routing is like how books are categorized in a library—filenames determine the URL path, and the directory structure serves as your routing table, without the need for any manual configuration.

1. What You'll Learn


2. A True Story of a Technical Manager

(1) Pain Point: Manual routing configuration is chaotic

Bob is the technical lead at an e-commerce company, and his team maintains a 50-page React SPA. Every time a new page is added, developers have to manually configure three files:

"Our routing configuration table routes.js has 300 lines. Last week, Xiao Ming added a new product detail page but forgot to register path: '/products/:id' in the routing table, resulting in a 404 error that went unnoticed for a full two hours after the page went live."

Bob's team encountered the following routing management issues:

Issue Impact Frequency
Missing routing configuration 404 after deployment 1–2 times per month
Manual Handling of Nested Routes Complex code, confusing route hierarchy Each new page
Scattered 404/500 Error Pages Inconsistent, poor user experience Multiple pages
Separate Maintenance of API Routes One set of routes for the front end and one for the back end Each API

(2) Solutions for Next.js File System Routing

The filename is the URL, and the directory structure is the routing table—there is no longer a need for react-router-dom routing configuration.

TEXT
src/app/
├── page.tsx                    # → /
├── about/
│   └── page.tsx                # → /about
├── products/
│   ├── page.tsx                # → /products
│   └── [id]/
│       ├── page.tsx            # → /products/1, /products/2
│       └── reviews/
│           └── page.tsx        # → /products/1/reviews
└── api/
    └── products/
        └── route.ts            # → /api/products (GET/POST)

(3) Revenue

Dimension Before (React Router) After (File System Routing)
Steps to Add a Page 3 steps (create a component + configure routing + import) 1 step (create a file)
Routing Configuration Maintenance 300-line routing table Zero lines, automatically determined by the directory
404 Errors Upon Launch 1–2 times per month 0 times
Unified Management of 404/500 Errors Manual import File conventions take effect automatically

3. Principles of File System Routing

(1) The filename is the URL

100%
graph TB
    subgraph "src/app/ Directory Structure"
        A[page.tsx] --> B[/]
        C[about/page.tsx] --> D[/about]
        E[products/page.tsx] --> F[/products]
        G[products/promo/page.tsx] --> H[/products/promo]
        I[products/id/page.tsx] --> J[/products/:id]
        K[dashboard/settings/page.tsx] --> L[/dashboard/settings]
    end

    style A fill:#d4edda
    style C fill:#d4edda
    style E fill:#d4edda
File Name Corresponding URL Description
app/page.tsx / Home
app/about/page.tsx /about Static Page
app/blog/page.tsx /blog Blog List
app/blog/[id]/page.tsx /blog/1 Dynamic Routing
app/dashboard/settings/page.tsx /dashboard/settings Nested Multi-Level

(2) Provisions in the Six Major Documents

File Name Purpose Required? Rendering Behavior
page.tsx Page Component (UI Content) Server Component (default)
layout.tsx Layout Container (Persistent State) ✅ Root layout Do not remount during navigation
loading.tsx Load skeleton screen Optional Suspense fallback
error.tsx Error Boundary UI Optional Capture Child Component Errors
not-found.tsx 404 Page Optional notFound() Trigger
route.ts API Endpoint Optional Server-side Execution

▶ Example: Creating Your First Page

Output:

TEXT
Diagram: page.tsx; /; about/page.tsx; /about; products/page.tsx; /products.
TSX
// ============================================
// Create /about Page
// Document:src/app/about/page.tsx
// Just create a file,Automatic Route Registration
// ============================================

export default function AboutPage() {
  return (
    <div className="max-w-2xl mx-auto p-8">
      <h1 className="text-3xl font-bold">About TaskFlow</h1>
      <p className="mt-4 text-gray-600 leading-relaxed">
        TaskFlow is a collaborative project management platform that helps
        teams plan, track, and deliver projects efficiently. Built with
        Next.js 16 and React 19, it provides real-time updates, seamless
        collaboration, and enterprise-grade security.
      </p>
      <div className="mt-8 grid grid-cols-3 gap-4">
        <div className="p-4 bg-blue-50 rounded-lg text-center">
          <div className="text-2xl font-bold text-blue-600">10K+</div>
          <div className="text-sm text-gray-500">Active Users</div>
        </div>
        <div className="p-4 bg-green-50 rounded-lg text-center">
          <div className="text-2xl font-bold text-green-600">50K+</div>
          <div className="text-sm text-gray-500">Projects</div>
        </div>
        <div className="p-4 bg-purple-50 rounded-lg text-center">
          <div className="text-2xl font-bold text-purple-600">99.9%</div>
          <div className="text-sm text-gray-500">Uptime</div>
        </div>
      </div>
    </div>
  );
}

Output:

TEXT
Content: About TaskFlow | TaskFlow is a collaborative project | 10K+ | Active Users

Output:

TEXT
Visit http://localhost:3000/about See:
About TaskFlow
TaskFlow is a collaborative project management platform...
10K+  Active Users | 50K+  Projects | 99.9%  Uptime

4. Dynamic Routing

(1) Single-Parameter Dynamic Routing [id]

100%
graph LR
    A[app/products] --> B[page.tsx → /products]
    A --> C[[id]]
    C --> D[page.tsx → /products/1]
    C --> E[/products/2]

    style B fill:#d4edda
    style D fill:#cce5ff
File Mode URL Example params Value
app/blog/[slug]/page.tsx /blog/hello-world { slug: 'hello-world' }
app/products/[id]/page.tsx /products/42 { id: '42' }
app/users/[userId]/settings/page.tsx /users/123/settings { userId: '123' }

▶ Example: Dynamic Product Details Page

Output:

TEXT
Diagram: app/products; page.tsx → /products; [id; page.tsx → /products/1; /products/2.
TSX
// ============================================
// Product Details Page — Dynamic Routing [id]
// Document:src/app/products/[id]/page.tsx
// ============================================

// In Next.js 16, params is a Promise (asynchronous)
export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const product = await fetch(`https://fakestoreapi.com/products/${id}`);

  return (
    <div className="container mx-auto p-8">
      <div className="flex gap-8">
        <img
          src={product.image}
          alt={product.title}
          className="w-80 h-80 object-contain"
        />
        <div>
          <h1 className="text-2xl font-bold">{product.title}</h1>
          <p className="text-xl text-green-600 font-bold mt-2">
            ${product.price}
          </p>
          <p className="text-sm text-gray-500 mt-1">
            Category: {product.category}
          </p>
          <p className="mt-4 text-gray-700">{product.description}</p>
        </div>
      </div>
    </div>
  );
}

Output:

TEXT
Server component fetches and renders data.

Output:

TEXT
Visit /products/1 See:
Fjallraven - Foldsack No. 1 Backpack
$109.95
Category: men's clothing
Your perfect pack for day trips and hikes...

(2) Catch-all Routes [...slug]

100%
graph TB
    A[app/docs] --> B[[...slug]]
    B --> C[page.tsx]
    C --> D[/docs/getting-started]
    C --> E[/docs/guides/installation]
    C --> F[/docs/api/authentication/overview]

    style C fill:#cce5ff
File Mode URL params.slug
app/docs/[...slug]/page.tsx /docs 404 (Level 1 is required)
app/docs/[...slug]/page.tsx /docs/getting-started ['getting-started']
app/docs/[...slug]/page.tsx /docs/guides/installation ['guides', 'installation']
app/docs/[...slug]/page.tsx /docs/a/b/c ['a', 'b', 'c']

▶ Example: Catch-all Documentation Page

Output:

TEXT
Diagram: app/docs; [...slug; page.tsx; /docs/getting-started; /docs/guides/installation; /docs/api/authentication/overview.
TSX
// ============================================
// Catch-all Routing — Multi-level Document Pages
// Document:src/app/docs/[...slug]/page.tsx
// ============================================

export default async function DocsPage({
  params,
}: {
  params: Promise<{ slug: string[] }>;
}) {
  const { slug } = await params;

  return (
    <div className="container mx-auto p-8">
      <nav className="text-sm text-gray-500 mb-4">
        Home / Docs / {slug.join(" / ")}
      </nav>
      <h1 className="text-3xl font-bold">Docs: {slug.join(" > ")}</h1>
      <div className="mt-8 p-6 bg-yellow-50 border border-yellow-200 rounded-lg">
        <p className="font-medium">You are viewing documentation for:</p>
        <ul className="mt-2 list-disc list-inside">
          {slug.map((segment, index) => (
            <li key={index}>
              Level {index + 1}: <code className="bg-gray-100 px-2 py-0.5 rounded">{segment}</code>
            </li>
          ))}
        </ul>
      </div>
      <p className="mt-4 text-gray-600">
        Total depth: {slug.length} levels
      </p>
    </div>
  );
}

Output:

TEXT
DocsPage renders its UI.

Output:

TEXT
Visit /docs/getting-started/installation See:
Home / Docs / getting-started / installation
Docs: getting-started > installation

You are viewing documentation for:
  - Level 1: getting-started
  - Level 2: installation
Total depth: 2 levels

(3) Optional Catch-all [[...catchAll]]

100%
graph TB
    A[app/categories] --> B[[[...catchAll]]]
    B --> C[page.tsx]
    C --> D[/categories]
    C --> E[/categories/electronics]
    C --> F[/categories/electronics/phones]

    style C fill:#d4edda
    style D fill:#d4edda
File Mode URL params.catchAll
app/categories/[[...catchAll]]/page.tsx /categories undefined
app/categories/[[...catchAll]]/page.tsx /categories/electronics ['electronics']
app/categories/[[...catchAll]]/page.tsx /categories/electronics/phones ['electronics', 'phones']

▶ Example: Optional Catch-all Category Page

Output:

TEXT
Diagram: app/categories; [[...catchAll; page.tsx; /categories; /categories/electronics; /categories/electronics/phones.
TSX
// ============================================
// Optional Catch-all — Category Browse Page
// Document:src/app/categories/[[...categories]]/page.tsx
// Both /categories and /categories/electronics take effect
// ============================================

export default async function CategoriesPage({
  params,
}: {
  params: Promise<{ categories?: string[] }>;
}) {
  const { categories } = await params;

  const allProducts = await fetch("https://fakestoreapi.com/products");
  const categoriesList = [...new Set(allProducts.map(p => p.category))];

  const filteredProducts = categories
    ? allProducts.filter(p => p.category === categories[0])
    : allProducts;

  return (
    <div className="container mx-auto p-8">
      <h1 className="text-3xl font-bold mb-6">
        {categories ? `Category: ${categories[0]}` : "All Categories"}
      </h1>

      {!categories && (
        <div className="flex gap-2 mb-8 flex-wrap">
          {categoriesList.map(cat => (
            <a
              key={cat}
              href={`/categories/${encodeURIComponent(cat)}`}
              className="px-4 py-2 bg-gray-100 rounded-full hover:bg-blue-100"
            >
              {cat}
            </a>
          ))}
        </div>
      )}

      <div className="grid grid-cols-4 gap-6">
        {filteredProducts.slice(0, 8).map(p => (
          <div key={p.id} className="border rounded-lg p-4 hover:shadow-lg">
            <img src={p.image} alt={p.title} className="h-40 mx-auto" />
            <p className="mt-2 font-medium text-sm truncate">{p.title}</p>
            <p className="text-green-600 font-bold">${p.price}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

Output:

TEXT
Server-side data fetch renders list of items.

Output:

TEXT
Visit /categories See:
All Category Buttons(electronics, jewelery, men's clothing, women's clothing)

Visit /categories/electronics See:
Category: electronics
Show only electronics(8Product Card)

5. loading.tsx and error.tsx

(1) loading.tsx — Load the skeleton screen

100%
graph LR
    A[User Navigation] --> B[loading.tsx<br/>Wireframe Display]
    B --> C[page.tsx<br/>Data Ready]
    C --> D[Full Page]

    style B fill:#f8d7da
    style C fill:#d4edda

▶ Example: Loading a skeleton screen

Output:

TEXT
Diagram: User Navigation; loading.tsx Wireframe Display; page.tsx Data Ready; Full Page.
TSX
// ============================================
// Loading the skeleton screen — Displayed when page data is retrieved
// Document:src/app/products/loading.tsx
// ============================================

export default function ProductsLoading() {
  return (
    <div className="container mx-auto p-8">
      <div className="h-8 w-48 bg-gray-200 rounded animate-pulse mb-6" />
      <div className="grid grid-cols-4 gap-6">
        {Array.from({ length: 8 }).map((_, i) => (
          <div key={i} className="border rounded-lg p-4">
            <div className="h-40 bg-gray-200 rounded animate-pulse" />
            <div className="h-4 bg-gray-200 rounded mt-2 animate-pulse" />
            <div className="h-4 w-16 bg-gray-200 rounded mt-2 animate-pulse" />
          </div>
        ))}
      </div>
    </div>
  );
}

Output:

TEXT
Renders a list of items using .map().

Output:

TEXT
When visiting /products, display until data loading is complete:
8 A gray placeholder card(Animation with Pulses)
After the data has finished loading,The placeholder content is automatically replaced with the actual content
Flicker-free、No white screen,Smooth Transition

(2) error.tsx — Error Boundary

▶ Example: Error Page

Output:

TEXT
The page renders as described above, with the UI updating based on the described behavior.
TSX
// ============================================
// Error Boundary — Catching Child Component Errors
// Document:src/app/products/error.tsx
// ============================================

'use client';

export default function ProductsError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div className="container mx-auto p-8 text-center">
      <div className="max-w-md mx-auto">
        <h2 className="text-2xl font-bold text-red-600 mb-4">
          Something went wrong!
        </h2>
        <p className="text-gray-600 mb-6">
          {error.message || "Failed to load products. Please try again."}
        </p>
        <button
          onClick={reset}
          className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
        >
          Try Again
        </button>
      </div>
    </div>
  );
}

Output:

TEXT
Content: Something went wrong! | Try Again

Output:

TEXT
When data retrieval from the product page fails,Show:
Something went wrong!
Failed to load products. Please try again.
[Try Again] button → Click to retry automatically

(3) not-found.tsx — 404 Page


6. Route Handlers (API Routes)

(1) Basic Usage of route.ts

100%
graph LR
    A[Client Request] --> B[route.ts]
    B --> C[GET /api/products]
    B --> D[POST /api/products]
    B --> E[PUT /api/products/:id]
    B --> F[DELETE /api/products/:id]

    style B fill:#cce5ff
HTTP Method Exported Function Purpose
GET export async function GET() Read Data
POST export async function POST() Create Data
PUT export async function PUT() Update Data
DELETE export async function DELETE() Delete Data
PATCH export async function PATCH() Partial Update

7. Complete Example: E-commerce Product Browsing System

TSX
// ============================================
// Comprehensive Example:E-commerce Product Browsing System
// Pages Covered、Layout、Loading、Error、API Routing
// ============================================

// src/app/layout.tsx — Root Layout
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className="bg-gray-50">
        <nav className="bg-white shadow-sm p-4">
          <a href="/" className="text-xl font-bold text-blue-600">
            ShopHub
          </a>
        </nav>
        <main>{children}</main>
      </body>
    </html>
  );
}

// src/app/page.tsx — Home
export default function HomePage() {
  return (
    <div className="container mx-auto p-8 text-center">
      <h1 className="text-4xl font-bold">Welcome to ShopHub</h1>
      <p className="mt-4 text-gray-600">
        Browse our collection of amazing products.
      </p>
      <a
        href="/products"
        className="inline-block mt-6 px-8 py-3 bg-blue-600 text-white rounded-lg"
      >
        Browse Products
      </a>
    </div>
  );
}

// src/app/products/page.tsx — Product List
export default async function ProductsPage() {
  const products = await fetch(
    "https://fakestoreapi.com/products"
  );

  return (
    <div className="container mx-auto p-8">
      <h1 className="text-3xl font-bold mb-6">All Products</h1>
      <div className="grid grid-cols-4 gap-6">
        {products.map(p => (
          <a
            key={p.id}
            href={`/products/${p.id}`}
            className="border rounded-lg p-4 bg-white hover:shadow-lg"
          >
            <img
              src={p.image}
              alt={p.title}
              className="h-40 mx-auto"
            />
            <p className="mt-2 font-medium text-sm">{p.title}</p>
            <p className="text-green-600 font-bold mt-1">${p.price}</p>
          </a>
        ))}
      </div>
    </div>
  );
}

// src/app/products/[id]/page.tsx — Product Details
export default async function ProductDetailPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const product = await fetch(
    `https://fakestoreapi.com/products/${id}`
  );

  if (!product) {
    notFound();
  }

  return (
    <div className="container mx-auto p-8">
      <a
        href="/products"
        className="text-blue-600 hover:underline mb-4 inline-block"
      >
        &larr; Back to products
      </a>
      <div className="flex gap-8 bg-white p-8 rounded-lg shadow">
        <img
          src={product.image}
          alt={product.title}
          className="w-96 h-96 object-contain"
        />
        <div>
          <h1 className="text-3xl font-bold">{product.title}</h1>
          <p className="text-2xl text-green-600 font-bold mt-4">
            ${product.price}
          </p>
          <p className="mt-6 text-gray-700 leading-relaxed">
            {product.description}
          </p>
          <button className="mt-6 px-8 py-3 bg-blue-600 text-white rounded-lg">
            Add to Cart
          </button>
        </div>
      </div>
    </div>
  );
}

// src/app/products/loading.tsx — Loading the skeleton screen
export default function ProductsLoading() {
  return (
    <div className="container mx-auto p-8">
      <div className="h-8 w-48 bg-gray-200 rounded animate-pulse mb-6" />
      <div className="grid grid-cols-4 gap-6">
        {Array.from({ length: 8 }).map((_, i) => (
          <div key={i} className="border rounded-lg p-4 bg-white">
            <div className="h-40 bg-gray-200 rounded animate-pulse" />
            <div className="h-4 bg-gray-200 rounded mt-2 animate-pulse" />
            <div className="h-4 w-16 bg-gray-200 rounded mt-2 animate-pulse" />
          </div>
        ))}
      </div>
    </div>
  );
}

// src/app/products/error.tsx — Error Boundary
'use client';
export default function ProductsError({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div className="container mx-auto p-8 text-center">
      <h2 className="text-2xl font-bold text-red-600">
        Failed to load products
      </h2>
      <button
        onClick={reset}
        className="mt-4 px-6 py-2 bg-blue-600 text-white rounded-lg"
      >
        Try Again
      </button>
    </div>
  );
}

Expected Output:

TEXT
Home(/):
  Welcome to ShopHub → [Browse Products]

Product List(/products):
  Loading... → 8 A gray skeleton screen
  Loading complete → Product Card Grid(Includes images、Title、Price)

Product Details(/products/1):
  Product Images (left) + Title/Price/Description/Add to Cart (right)
  ← Back to products Link Back to List

Error Status:
  Failed to load products
  [Try Again] button

404 Status:
  Automatically from the root not-found.tsx Processing

❓ FAQ

Q Can I use .jsx instead of .tsx for page.tsx?
A Yes. If TypeScript is not enabled in your project, you can use the .jsx extension. However, this tutorial recommends TypeScript, so all code examples use .tsx.
Q Why are params Promises in Next.js 16?
A Starting with Next.js 15, params, searchParams, and similar variables were changed to Promises to support asynchronous page generation. If you use them directly params.id, you’ll encounter a TypeScript error; you must await params to retrieve their values.
Q What is the relationship between loading.tsx and Suspense?
A loading.tsx is a page-level Suspense fallback. Next.js automatically wraps loading.tsx in &lt;Suspense&gt;. If you need a component-level loading state, you should manually use &lt;Suspense fallback={...}&gt;.
Q Why does error.tsx need use client?
A error.tsx must be a Client Component because it needs to handle interactions (such as the reset button). Server Components cannot contain event handlers or hooks, so the 'use client' directive is required.
Q Can route.ts and page.tsx coexist?
A No. Under the same route segment, page.tsx and route.ts are mutually exclusive. A directory can only contain one type of route handler—either a page (page.tsx) or an API (route.ts).
Q What exactly is the difference between [...slug] and [[...catchAll]]?
A [...slug] requires at least one path segment (/docs/a matches; /docs does not match and returns a 404). [[...catchAll]] allows zero path segments (both /categories and /categories/a match), making it suitable for "optional category browsing" scenarios.

📖 Summary


📝 Exercises

  1. Basic Exercise (⭐): Create a page named app/blog/[slug]/page.tsx in the project so that when /blog/hello-world is accessed, the console outputs slug: hello-world.

  2. Advanced Exercise (⭐⭐): Create app/api/todos/route.ts to implement a simple TODO API (GET returns a list, POST creates a TODO), and verify it by accessing it in a browser.

  3. Challenge (⭐⭐⭐): Create two implementations, app/docs/[...slug]/page.tsx and app/docs/[[...slug]]/page.tsx, to test the access behavior of /docs, and use a table to compare the differences between the two.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏