Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
menu
  1. Home
  2. /Web Development
  3. /Nextjs
  4. /Catch All Routes
Your Progress0%
0 of 70 completed

NextJS Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication

Catch-All and Optional Catch-All Routes

Matching multiple URL segments with flexible routing patterns

Regular dynamic routes match one segment. But what if you need to match any number of segments? Documentation sites with nested pages, file browsers with deep folder structures, or category systems with unlimited nesting all need this flexibility. That's where catch-all routes come in, using the [...slug] and [[...slug]] patterns.

The Problem with Regular Dynamic Routes

Let's say you're building a documentation site. You want these URLs to all work:

  • /docs/introduction
  • /docs/getting-started/installation
  • /docs/api/components/button
  • /docs/guides/deployment/vercel/setup

With regular dynamic routes, you'd need to know the maximum depth ahead of time:

PLAINTEXT
app/
  docs/
    [level1]/
      page.tsx                           → /docs/introduction
      [level2]/
        page.tsx                         → /docs/getting-started/installation
        [level3]/
          page.tsx                       → /docs/api/components/button
          [level4]/
            page.tsx                     → /docs/guides/deployment/vercel/setup
            [level5]/                    → What if you need more depth?
              page.tsx

This is terrible! You're duplicating code, limiting depth, and making maintenance a nightmare. There has to be a better way...

The Solution: Catch-All Routes

Catch-all routes use [...slug] to match any number of segments:

PLAINTEXT
app/
  docs/
    [...slug]/
      page.tsx        ← One file handles ALL nested paths!

Now this single route handles:

  • /docs/introduction
  • /docs/getting-started/installation
  • /docs/api/components/button
  • /docs/guides/deployment/vercel/setup/advanced/optimization
  • ...any depth you need!

The Three Dots (...)

The ... syntax is called the "spread" or "rest" operator. It means "capture all remaining segments." Think of it as saying "and everything after this."

Basic Catch-All: [...slug]

The [...slug] pattern matches one or more segments:

Creating a Basic Catch-All Route

PLAINTEXT
app/
  docs/
    page.tsx          → /docs (home page)
    [...slug]/
      page.tsx        → /docs/* (everything else)
app/docs/[...slug]/page.tsx
interface PageProps {
  params: {
    slug: string[];  // Array of all segments!
  };
}

export default function DocsPage({ params }: PageProps) {
  // URL: /docs/getting-started
  // params.slug = ["getting-started"]
  
  // URL: /docs/api/components/button
  // params.slug = ["api", "components", "button"]
  
  // URL: /docs/advanced/features/auth/setup
  // params.slug = ["advanced", "features", "auth", "setup"]
  
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-4xl font-bold mb-4">Documentation</h1>
      
      {/* Show the path */}
      <div className="text-gray-600 mb-8">
        Path: {params.slug.join(' / ')}
      </div>
      
      {/* Render content based on slug */}
      <div className="prose max-w-none">
        <p>Showing content for: {params.slug.join('/')}</p>
      </div>
    </div>
  );
}

Catch-All Route Matching

See how [...slug] captures multiple segments

šŸ“ File Structure

app/
  docs/
    [...slug]/
      page.tsx

🌐 URL Path

/docs/getting-started
Dynamic Route

params.slug = ["getting-started"]

šŸ“Š Slug is an Array

Unlike regular dynamic routes where params.slug is a string, catch-all routes return an array containing all matched segments.

Optional Catch-All: [[...slug]]

The [[...slug]] pattern (double brackets) matches zero or more segments:

The Key Difference

[...slug] - Required

Matches:

  • āœ… /docs/intro
  • āœ… /docs/api/setup
  • āŒ /docs (no match)

Requires at least one segment. You need a separate page.tsx for /docs

[[...slug]] - Optional

Matches:

  • āœ… /docs (no segments)
  • āœ… /docs/intro
  • āœ… /docs/api/setup

Matches the parent route too! No separate page.tsx needed.

PLAINTEXT
app/
  docs/
    [[...slug]]/
      page.tsx        → Handles /docs AND /docs/* (everything!)
app/docs/[[...slug]]/page.tsx
interface PageProps {
  params: {
    slug?: string[];  // Optional! Can be undefined
  };
}

export default function DocsPage({ params }: PageProps) {
  // URL: /docs
  // params.slug = undefined
  
  // URL: /docs/getting-started
  // params.slug = ["getting-started"]
  
  // URL: /docs/api/components/button
  // params.slug = ["api", "components", "button"]
  
  // Handle the root case
  if (!params.slug) {
    return (
      <div className="container mx-auto px-4 py-8">
        <h1 className="text-4xl font-bold mb-4">Documentation Home</h1>
        <p>Welcome to our documentation!</p>
      </div>
    );
  }
  
  // Handle nested paths
  const path = params.slug.join('/');
  
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-4xl font-bold mb-4">
        {params.slug[params.slug.length - 1]}
      </h1>
      <p className="text-gray-600">Path: {path}</p>
    </div>
  );
}

Optional Catch-All Matching

[[...slug]] matches the parent route too

šŸ“ File Structure

app/
  docs/
    [[...slug]]/
      page.tsx

🌐 URL Path

/docs
Dynamic Route

params.slug = undefined (optional catch-all matches root)

Working with Segment Arrays

Since params.slug is an array, you can use array methods to navigate and build logic:

Common Operations

TYPESCRIPT
export default function DocsPage({ 
  params 
}: { 
  params: { slug?: string[] } 
}) {
  const slug = params.slug || [];
  
  // Get the number of segments
  const depth = slug.length;
  // /docs/api/auth → depth = 2
  
  // Get first segment (category)
  const category = slug[0];
  // /docs/api/auth → category = "api"
  
  // Get last segment (page name)
  const pageName = slug[slug.length - 1];
  // /docs/api/auth → pageName = "auth"
  
  // Join into path
  const fullPath = slug.join('/');
  // /docs/api/auth → fullPath = "api/auth"
  
  // Check if in specific section
  const isApiDocs = slug[0] === 'api';
  
  // Get parent path
  const parentPath = slug.slice(0, -1).join('/');
  // /docs/api/auth → parentPath = "api"
  
  return <div>{/* Use these values */}</div>;
}

Building Breadcrumbs

TYPESCRIPT
import Link from 'next/link';

export default function DocsPage({ 
  params 
}: { 
  params: { slug?: string[] } 
}) {
  const slug = params.slug || [];
  
  // Build breadcrumb items
  const breadcrumbs = [
    { label: 'Docs', href: '/docs' },
    ...slug.map((segment, index) => ({
      label: segment.replace(/-/g, ' '),
      href: `/docs/${slug.slice(0, index + 1).join('/')}`,
    })),
  ];
  
  return (
    <div className="container mx-auto px-4 py-8">
      {/* Breadcrumbs */}
      <nav className="flex items-center gap-2 text-sm mb-6">
        {breadcrumbs.map((crumb, index) => (
          <div key={crumb.href} className="flex items-center gap-2">
            {index > 0 && <span className="text-gray-400">/</span>}
            <Link 
              href={crumb.href}
              className="text-blue-600 hover:underline capitalize"
            >
              {crumb.label}
            </Link>
          </div>
        ))}
      </nav>
      
      {/* Page content */}
      <h1 className="text-4xl font-bold">
        {slug[slug.length - 1]?.replace(/-/g, ' ') || 'Home'}
      </h1>
    </div>
  );
}

Fetching Data with Catch-All Routes

Use the slug array to fetch the right content:

app/docs/[[...slug]]/page.tsx
import { notFound } from 'next/navigation';

interface DocContent {
  title: string;
  content: string;
  category: string;
}

interface PageProps {
  params: { slug?: string[] };
}

export default async function DocsPage({ params }: PageProps) {
  const slug = params.slug || [];
  const path = slug.join('/');
  
  // Fetch content based on the path
  const res = await fetch(
    `https://api.example.com/docs/${path || 'index'}`
  );
  
  if (!res.ok) {
    notFound();
  }
  
  const doc: DocContent = await res.json();
  
  return (
    <article className="container mx-auto px-4 py-8 max-w-4xl">
      <header className="mb-8">
        <div className="text-sm text-gray-600 mb-2">
          {doc.category}
        </div>
        <h1 className="text-4xl font-bold">{doc.title}</h1>
      </header>
      
      <div className="prose max-w-none">
        {doc.content}
      </div>
      
      {/* Navigation based on depth */}
      {slug.length > 0 && (
        <footer className="mt-12 pt-6 border-t">
          <Link 
            href={slug.length > 1 
              ? `/docs/${slug.slice(0, -1).join('/')}`
              : '/docs'
            }
            className="text-blue-600 hover:underline"
          >
            ← Back to {slug.length > 1 ? 'Parent' : 'Home'}
          </Link>
        </footer>
      )}
    </article>
  );
}

Real-World Use Cases

1. Documentation Site

PLAINTEXT
app/
  docs/
    [[...slug]]/
      page.tsx

Handles:
  /docs                           → Home
  /docs/introduction              → Introduction
  /docs/getting-started/install   → Installation guide
  /docs/api/components/button     → Button API docs

2. File Browser/Explorer

PLAINTEXT
app/
  files/
    [...path]/
      page.tsx

Handles:
  /files/documents                → Documents folder
  /files/documents/2024           → 2024 subfolder
  /files/documents/2024/reports   → Reports subfolder
  /files/photos/vacation/hawaii   → Deep nesting
app/files/[...path]/page.tsx
export default async function FileBrowserPage({
  params,
}: {
  params: { path: string[] };
}) {
  const currentPath = params.path.join('/');
  
  // Fetch files and folders at this path
  const items = await fetch(`/api/files?path=${currentPath}`)
    .then(r => r.json());
  
  return (
    <div className="p-8">
      <h1 className="text-2xl font-bold mb-4">
        /{currentPath}
      </h1>
      
      <div className="grid grid-cols-4 gap-4">
        {items.map((item) => (
          <Link
            key={item.name}
            href={`/files/${currentPath}/${item.name}`}
            className="p-4 border rounded hover:shadow-lg"
          >
            <div className="text-4xl mb-2">
              {item.type === 'folder' ? 'šŸ“' : 'šŸ“„'}
            </div>
            <div className="font-semibold">{item.name}</div>
          </Link>
        ))}
      </div>
    </div>
  );
}

3. Category Hierarchy (E-commerce)

PLAINTEXT
app/
  shop/
    categories/
      [...category]/
        page.tsx

Handles:
  /shop/categories/electronics
  /shop/categories/electronics/computers
  /shop/categories/electronics/computers/laptops
  /shop/categories/electronics/computers/laptops/gaming

4. Multi-Language Routes

PLAINTEXT
app/
  [locale]/
    [[...slug]]/
      page.tsx

Handles:
  /en                           → English home
  /en/about                     → English about
  /es/productos/electronicos    → Spanish electronics
  /fr/docs/api/authentication   → French API docs

Catch-All Routes in Practice

See real-world catch-all route structures

appImportant

Select a file or folder to see details

Generating Static Paths

For catch-all routes you want to pre-render, use generateStaticParams:

app/docs/[...slug]/page.tsx
// Generate all doc paths at build time
export async function generateStaticParams() {
  const docs = await fetch('https://api.example.com/docs/all')
    .then(res => res.json());
  
  // Return array of slug arrays
  return docs.map((doc: { path: string }) => ({
    slug: doc.path.split('/'),  // Convert "api/auth" to ["api", "auth"]
  }));
}

// Example return value:
// [
//   { slug: ["introduction"] },
//   { slug: ["getting-started", "installation"] },
//   { slug: ["api", "components", "button"] },
//   { slug: ["guides", "deployment", "vercel"] },
// ]

export default async function DocsPage({
  params,
}: {
  params: { slug: string[] };
}) {
  const path = params.slug.join('/');
  const doc = await fetch(`https://api.example.com/docs/${path}`)
    .then(res => res.json());
  
  return <article>{doc.content}</article>;
}

Combining Route Patterns

You can mix catch-all with regular routes and dynamic routes:

PLAINTEXT
app/
  blog/
    page.tsx                  → /blog (listing)
    [id]/
      page.tsx                → /blog/123 (single post by ID)
    category/
      [...slug]/
        page.tsx              → /blog/category/tech/tutorials
    archive/
      [[...date]]/
        page.tsx              → /blog/archive or /blog/archive/2024/01

Route Priority:

  1. Static routes (exact matches)
  2. Dynamic routes [id]
  3. Catch-all routes [...slug]
  4. Optional catch-all [[...slug]]

Be Careful with Overlaps

If you have both /blog/[id]/page.tsx and /blog/[...slug]/page.tsx, the single dynamic route takes priority. So /blog/123 matches [id], but /blog/tech/tutorials matches [...slug].

Practice: Build a Documentation Site

Let's build a complete documentation site with catch-all routes:

Documentation Site with Optional Catch-All

Try navigating: /docs, /docs/introduction, /docs/api/authentication

page.tsx

Output Preview

Click "Run Code" to see the output

šŸŽÆ Challenge Exercise

Build these in your Next.js project:

  1. A file explorer: app/files/[...path]/page.tsx
  2. A category browser: app/categories/[...category]/page.tsx
  3. A blog archive: app/blog/archive/[[...date]]/page.tsx

Best Practices

1. Always Handle the Empty Case

TYPESCRIPT
export default function Page({ 
  params 
}: { 
  params: { slug?: string[] } 
}) {
  const slug = params.slug || [];
  
  // Always have a fallback
  if (slug.length === 0) {
    return <HomePage />;
  }
  
  // Handle nested paths
  return <NestedPage slug={slug} />;
}

2. Validate Segment Depth

TYPESCRIPT
export default function Page({ params }) {
  const slug = params.slug || [];
  
  // Limit maximum depth
  if (slug.length > 5) {
    notFound();
  }
  
  // Or require minimum depth
  if (slug.length < 2) {
    return <div>Please specify a category and subcategory</div>;
  }
}

3. Sanitize Slugs

TYPESCRIPT
export default function Page({ params }) {
  const slug = params.slug || [];
  
  // Sanitize each segment
  const safeSlugs = slug.map(segment =>
    segment.toLowerCase().replace(/[^a-z0-9-]/g, '')
  );
  
  // Use sanitized slugs
  const path = safeSlugs.join('/');
}

4. Provide Clear Navigation

Always show breadcrumbs or a way to navigate up the hierarchy:

TYPESCRIPT
// Build parent link
const parentHref = slug.length > 1
  ? `/docs/${slug.slice(0, -1).join('/')}`
  : '/docs';

return (
  <div>
    <Link href={parentHref}>← Back</Link>
    {/* content */}
  </div>
);

Quick Reference: Route Pattern Comparison

PatternMatchesParams TypeUse Case
[slug]One segmentstringSingle dynamic segment
[...slug]One or more segmentsstring[]Multi-level paths
[[...slug]]Zero or more segmentsstring[] | undefinedOptional multi-level

Key Takeaways

  • [...slug] matches one or more segments - requires at least one
  • [[...slug]] matches zero or more segments - optional, includes parent route
  • params.slug is an array - contains all matched segments
  • Perfect for documentation, file browsers, categories - any deep nesting
  • Use array methods - slice, join, map to work with segments
  • Always handle empty case - especially with optional catch-all
  • Validate depth and sanitize - prevent security issues
  • Provide clear navigation - breadcrumbs and parent links

What's Next?

You've now mastered all types of dynamic routing in Next.js! From single segments with [slug] to unlimited nesting with [...slug]. But there's another powerful organizational feature to learn: route groups.

In the next lesson, we'll explore how to use parentheses (folder) to organize your routes into logical groups without affecting the URL. This is perfect for organizing large applications, applying different layouts to different sections, and keeping your file structure clean!

šŸŽ“ You're Becoming a Routing Expert!

Catch-all routes might seem complex at first, but they're incredibly powerful once you understand them. They're the secret behind flexible, scalable routing systems in production apps. Keep practicing!

Test Your Understanding

Question 1 of 4

What does the [...slug] pattern match?

Master Next.js catch-all routes! Learn [...slug] and [[...slug]] patterns for flexible multi-segment routing.

Previous
Dynamic Routes and Route Parameters
Next
Route Groups for Organization

Continue Mastering Next.js

Join 2,000+ developers building with Next.js. Get the next lesson on route groups delivered to your inbox - absolutely FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

NextJS Tutorials

0 of 70 completed

Your Progress0%

Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

Ā© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo