We use cookies to enhance your experience on the site
CodeWorlds

Nested Paths and Catch-All Parameters

In the Quantum Metropolis, the transportation system enables residents to reach every corner of the city, and even unexplored territories. Special Q-Hopper vehicles can collect multiple travel destinations and visit them all in sequence, dynamically adapting to passenger needs. This flexibility is key to the functioning of such an advanced urban structure.

In the world of Next.js 15, similar flexibility is provided by advanced routing features such as nested paths and catch-all parameters. They allow handling complex URL structures and dynamic route generation based on data.

Dynamic Nested Paths

In Next.js 15, we can create nested dynamic paths by combining static and dynamic segments at different levels of the hierarchy:

1app/
2  ├── destinations/              # /destinations
3  │   ├── page.tsx               # Destinations list page
4  │   └── [destinationId]/       # Dynamic segment for destination ID
5  │       ├── page.tsx           # /destinations/:destinationId
6  │       └── attractions/       # /destinations/:destinationId/attractions
7  │           ├── page.tsx       # Attractions page for the destination
8  │           └── [attractionId] # Nested dynamic segment
9  │               └── page.tsx   # /destinations/:destinationId/attractions/:attractionId

In this structure, we have two levels of dynamic segments:

[destinationId]
and
[attractionId]
. This allows creating paths such as
/destinations/mars/attractions/olympus-mons
.

Implementing Dynamic Nested Paths

Here's an example of how to implement a nested paths system for our Quantum Voyages application:

1// app/destinations/[destinationId]/page.tsx
2import { getDestinationById } from '@/lib/api';
3import { DestinationDetails } from '@/components/destinations/DestinationDetails';
4import { AttractionsPreview } from '@/components/destinations/AttractionsPreview';
5import Link from 'next/link';
6import { notFound } from 'next/navigation';
7
8// Generating static parameters for the most popular destinations
9export async function generateStaticParams() {
10  // Fetch the most popular destinations for prerendering
11  const destinations = await getPopularDestinations();
12  
13  return destinations.map((destination) => ({
14    destinationId: destination.id,
15  }));
16}
17
18export default async function DestinationPage({ params }: { params: Promise<{ destinationId: string }> }) {
19  const { destinationId } = await params;
20  const destination = await getDestinationById(destinationId);
21  
22  // If the destination doesn't exist, show the 404 page
23  if (!destination) {
24    notFound();
25  }
26  
27  return (
28    <div className="container mx-auto px-4 py-12">
29      <h1 className="text-4xl font-bold mb-6">{destination.name}</h1>
30      
31      <DestinationDetails destination={destination} />
32      
33      <section className="mt-12">
34        <div className="flex justify-between items-center mb-6">
35          <h2 className="text-2xl font-semibold">Popular Attractions</h2>
36          <Link 
37            href={`/destinations/\${destinationId}/attractions\`}
38            className="text-indigo-600 hover:underline"
39          >
40            See all attractions
41          </Link>
42        </div>
43        
44        <AttractionsPreview destinationId={destinationId} />
45      </section>
46    </div>
47  );
48}
1// app/destinations/[destinationId]/attractions/page.tsx
2import { getDestinationById, getAttractionsForDestination } from '@/lib/api';
3import Link from 'next/link';
4import Image from 'next/image';
5import { notFound } from 'next/navigation';
6
7export default async function DestinationAttractionsPage({ params }: { params: Promise<{ destinationId: string }> }) {
8  const { destinationId } = await params;
9  const destination = await getDestinationById(destinationId);
10  
11  if (!destination) {
12    notFound();
13  }
14  
15  const attractions = await getAttractionsForDestination(destinationId);
16  
17  return (
18    <div className="container mx-auto px-4 py-12">
19      <div className="flex items-center gap-2 mb-8 text-sm text-gray-500">
20        <Link href="/destinations" className="hover:text-indigo-600">Destinations</Link>
21        <span>/</span>
22        <Link href={`/destinations/\${destinationId}\`} className="hover:text-indigo-600">
23          {destination.name}
24        </Link>
25        <span>/</span>
26        <span>Attractions</span>
27      </div>
28      
29      <h1 className="text-3xl font-bold mb-2">Attractions at {destination.name}</h1>
30      <p className="text-gray-600 mb-8">{destination.description}</p>
31      
32      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
33        {attractions.map((attraction) => (
34          <Link
35            key={attraction.id}
36            href={`/destinations/\${destinationId}/attractions/\${attraction.id}\`}
37            className="block group"
38          >
39            <div className="rounded-lg overflow-hidden shadow-md hover:shadow-lg transition-shadow">
40              <div className="relative h-48">
41                <Image
42                  src={attraction.imageUrl}
43                  alt={attraction.name}
44                  fill
45                  className="object-cover transition-transform group-hover:scale-105"
46                />
47              </div>
48              <div className="p-4">
49                <h2 className="text-xl font-semibold mb-2">{attraction.name}</h2>
50                <p className="text-gray-600 line-clamp-2">{attraction.shortDescription}</p>
51              </div>
52            </div>
53          </Link>
54        ))}
55      </div>
56    </div>
57  );
58}
1// app/destinations/[destinationId]/attractions/[attractionId]/page.tsx
2import { getDestinationById, getAttractionById } from '@/lib/api';
3import Link from 'next/link';
4import Image from 'next/image';
5import { notFound } from 'next/navigation';
6
7export default async function AttractionPage({ 
8  params 
9}: { 
10  params: Promise<{ destinationId: string; attractionId: string }> 
11}) {
12  const { destinationId, attractionId } = await params;
13  
14  const [destination, attraction] = await Promise.all([
15    getDestinationById(destinationId),
16    getAttractionById(destinationId, attractionId)
17  ]);
18  
19  if (!destination || !attraction) {
20    notFound();
21  }
22  
23  return (
24    <div className="container mx-auto px-4 py-12">
25      <div className="flex items-center gap-2 mb-8 text-sm text-gray-500">
26        <Link href="/destinations" className="hover:text-indigo-600">Destinations</Link>
27        <span>/</span>
28        <Link href={`/destinations/\${destinationId}\`} className="hover:text-indigo-600">
29          {destination.name}
30        </Link>
31        <span>/</span>
32        <Link href={`/destinations/\${destinationId}/attractions\`} className="hover:text-indigo-600">
33          Attractions
34        </Link>
35        <span>/</span>
36        <span>{attraction.name}</span>
37      </div>
38      
39      <div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
40        <div className="relative h-[400px] lg:h-[600px] rounded-lg overflow-hidden">
41          <Image
42            src={attraction.imageUrl}
43            alt={attraction.name}
44            fill
45            className="object-cover"
46            priority
47          />
48        </div>
49        
50        <div>
51          <h1 className="text-4xl font-bold mb-4">{attraction.name}</h1>
52          <div className="flex items-center mb-6">
53            <span className="px-3 py-1 bg-indigo-100 text-indigo-800 rounded-full text-sm">
54              {attraction.category}
55            </span>
56            <span className="mx-4 text-gray-300">|</span>
57            <div className="flex items-center text-amber-500">
58              {/* Rating stars */}
59              {Array.from({ length: 5 }).map((_, i) => (
60                <svg key={i} xmlns="http://www.w3.org/2000/svg" className={`h-5 w-5 \${i < attraction.rating ? 'fill-current' : 'text-gray-300'}\`} viewBox="0 0 20 20" fill="currentColor">
61                  <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
62                </svg>
63              ))}
64              <span className="ml-2 text-gray-700">{attraction.rating.toFixed(1)}</span>
65            </div>
66          </div>
67          
68          <p className="text-gray-700 mb-6">{attraction.description}</p>
69          
70          <div className="space-y-4 mb-8">
71            <div className="flex">
72              <div className="w-32 font-semibold">Location:</div>
73              <div>{attraction.location}</div>
74            </div>
75            <div className="flex">
76              <div className="w-32 font-semibold">Visiting Cost:</div>
77              <div>{attraction.price} credits</div>
78            </div>
79            <div className="flex">
80              <div className="w-32 font-semibold">Visiting Time:</div>
81              <div>{attraction.duration}</div>
82            </div>
83          </div>
84          
85          <button className="w-full py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors">
86            Book a Visit
87          </button>
88        </div>
89      </div>
90    </div>
91  );
92}

Catch-All Parameters

In the Quantum Metropolis, Q-Route technology enables traveling to any number of places based on a single route code. Similarly, in Next.js 15, catch-all parameters enable handling paths with any number of segments.

Catch-All Parameter Syntax

Catch-all parameters are denoted by three dots before the parameter name in square brackets:

1[...param]

This notation allows matching any number of URL segments, which are passed as an array to the component.

Directory Structure

1app/
2  ├── shop/                   # /shop
3  │   └── [...categories]/    # /shop/category1
4  │       └── page.tsx        # /shop/category1/category2/category3/etc.

In this example, the path

/shop/clothing/men/shirts
will be matched by
[...categories]
and the
categories
parameter will contain the array
['clothing', 'men', 'shirts']
.

Implementation for Quantum Voyages

Implementation of a product category system with catch-all parameters:

1// app/shop/[...categories]/page.tsx
2import { fetchCategorizedProducts } from '@/lib/api/products';
3import { Breadcrumbs } from '@/components/shop/Breadcrumbs';
4import { ProductGrid } from '@/components/shop/ProductGrid';
5import { CategoryFilters } from '@/components/shop/CategoryFilters';
6
7export default async function CategoriesPage({ params }: { params: Promise<{ categories: string[] }> }) {
8  const { categories } = await params;
9  
10  // Fetching products based on all provided categories
11  const { products, availableFilters } = await fetchCategorizedProducts(categories);
12  
13  // Constructing the title based on the last category
14  const currentCategory = categories[categories.length - 1];
15  const formattedCategoryName = currentCategory
16    .split('-')
17    .map(word => word.charAt(0).toUpperCase() + word.slice(1))
18    .join(' ');
19  
20  return (
21    <div className="container mx-auto px-4 py-12">
22      <Breadcrumbs categories={categories} />
23      
24      <h1 className="text-3xl font-bold mb-8">{formattedCategoryName}</h1>
25      
26      <div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
27        <div className="lg:col-span-1">
28          <CategoryFilters 
29            categories={categories} 
30            availableFilters={availableFilters} 
31          />
32        </div>
33        
34        <div className="lg:col-span-3">
35          <ProductGrid products={products} />
36        </div>
37      </div>
38    </div>
39  );
40}

Helper Components

1// components/shop/Breadcrumbs.tsx
2import Link from 'next/link';
3
4export function Breadcrumbs({ categories }: { categories: string[] }) {
5  return (
6    <div className="flex items-center flex-wrap gap-2 mb-8 text-sm text-gray-500">
7      <Link href="/shop" className="hover:text-indigo-600">
8        Shop
9      </Link>
10      
11      {categories.map((category, index) => {
12        // Building the URL path for this category
13        const url = `/shop/\${categories.slice(0, index + 1).join('/')}`;
14        const isLast = index === categories.length - 1;
15        
16        const formattedName = category
17          .split('-')
18          .map(word => word.charAt(0).toUpperCase() + word.slice(1))
19          .join(' ');
20        
21        return (
22          <div key={category} className="flex items-center">
23            <span className="mx-2">/</span>
24            {isLast ? (
25              <span>{formattedName}</span>
26            ) : (
27              <Link href={url} className="hover:text-indigo-600">
28                {formattedName}
29              </Link>
30            )}
31          </div>
32        );
33      })}
34    </div>
35  );
36}

Optional Catch-All Parameters

In Next.js 15, we can also define optional catch-all parameters using double square brackets:

1[[...param]]

The main difference is that an optional catch-all parameter will also match the path without any segments. For example,

app/shop/[[...categories]]/page.tsx
will match:

  • /shop
    (in this case,
    categories
    will be an empty array
    []
    )
  • /shop/clothing
    (
    categories
    =
    ['clothing']
    )
  • /shop/clothing/men/shirts
    (
    categories
    =
    ['clothing', 'men', 'shirts']
    )

Implementing a Shop with Optional Catch-All Parameters

1// app/shop/[[...categories]]/page.tsx
2import { fetchCategorizedProducts, fetchFeaturedProducts } from '@/lib/api/products';
3import { Breadcrumbs } from '@/components/shop/Breadcrumbs';
4import { ProductGrid } from '@/components/shop/ProductGrid';
5import { CategoryFilters } from '@/components/shop/CategoryFilters';
6import { FeaturedProducts } from '@/components/shop/FeaturedProducts';
7import { CategoryPreview } from '@/components/shop/CategoryPreview';
8
9export default async function ShopPage({ params }: { params: Promise<{ categories?: string[] }> }) {
10  const categories = (await params).categories || [];
11  const isRootShopPage = categories.length === 0;
12  
13  if (isRootShopPage) {
14    // We're on the main shop page
15    const featuredProducts = await fetchFeaturedProducts();
16    const topCategories = await fetchTopCategories();
17    
18    return (
19      <div className="container mx-auto px-4 py-12">
20        <h1 className="text-4xl font-bold mb-12">Shop Quantum Voyages</h1>
21        
22        <section className="mb-16">
23          <h2 className="text-2xl font-semibold mb-8">Featured Products</h2>
24          <FeaturedProducts products={featuredProducts} />
25        </section>
26        
27        <section>
28          <h2 className="text-2xl font-semibold mb-8">Browse Categories</h2>
29          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
30            {topCategories.map(category => (
31              <CategoryPreview key={category.id} category={category} />
32            ))}
33          </div>
34        </section>
35      </div>
36    );
37  } else {
38    // We're on a category page
39    const { products, availableFilters } = await fetchCategorizedProducts(categories);
40    
41    // Constructing the title based on the last category
42    const currentCategory = categories[categories.length - 1];
43    const formattedCategoryName = currentCategory
44      .split('-')
45      .map(word => word.charAt(0).toUpperCase() + word.slice(1))
46      .join(' ');
47    
48    return (
49      <div className="container mx-auto px-4 py-12">
50        <Breadcrumbs categories={categories} />
51        
52        <h1 className="text-3xl font-bold mb-8">{formattedCategoryName}</h1>
53        
54        <div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
55          <div className="lg:col-span-1">
56            <CategoryFilters 
57              categories={categories} 
58              availableFilters={availableFilters} 
59            />
60          </div>
61          
62          <div className="lg:col-span-3">
63            <ProductGrid products={products} />
64          </div>
65        </div>
66      </div>
67    );
68  }
69}

Accessing Parameters in Components

In Next.js 15, we can access path parameters in several ways:

In Server Components

1// Server Component
2export default async function ProductPage({ params }: { params: Promise<{ productId: string }> }) {
3  // Using (await params).productId
4  return <div>Product ID: {(await params).productId}</div>;
5}

In Client Components

In client-side components, we can use the

useParams
hook from
next/navigation
:

1'use client';
2
3import { useParams } from 'next/navigation';
4
5export default function ClientProductDetails() {
6  const params = useParams();
7  const productId = params.productId;
8  
9  return <div>Viewing product: {productId}</div>;
10}

In Generation Functions

Next.js 15 offers functions for generating static parameters and metadata:

1// Generating static parameters
2export async function generateStaticParams() {
3  const products = await fetchTopProducts();
4  
5  return products.map((product) => ({
6    productId: product.id,
7  }));
8}
9
10// Generating metadata
11export async function generateMetadata({ params }: { params: Promise<{ productId: string }> }) {
12  const product = await fetchProductById((await params).productId);
13  
14  return {
15    title: product.name,
16    description: product.description,
17    openGraph: {
18      images: [{ url: product.imageUrl }],
19    },
20  };
21}

Parameter Validation and Handling

When working with dynamic parameters, it's important to validate them:

1export default async function ProductPage({ params }: { params: Promise<{ productId: string }> }) {
2  // Check if productId is a valid UUID
3  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test((await params).productId)) {
4    // Throw an error that will be caught by the nearest error.js
5    throw new Error('Invalid product ID format');
6  }
7  
8  const product = await fetchProductById((await params).productId);
9  
10  // If the product doesn't exist, show the 404 page
11  if (!product) {
12    notFound();
13  }
14  
15  return <ProductDetails product={product} />;
16}

Error and Missing Resource Handling

Next.js 15 enables elegant handling of situations when a resource is not found or an error occurs:

Error Handling with error.js

1// app/products/[productId]/error.tsx
2'use client';
3
4import { useEffect } from 'react';
5
6export default function ProductError({
7  error,
8  reset,
9}: {
10  error: Error & { digest?: string };
11  reset: () => void;
12}) {
13  useEffect(() => {
14    // You can log the error to an external service
15    console.error(error);
16  }, [error]);
17
18  return (
19    <div className="container mx-auto px-4 py-16 text-center">
20      <h2 className="text-2xl font-bold mb-4">Something went wrong!</h2>
21      <p className="mb-8 text-gray-600">{error.message}</p>
22      <button
23        onClick={reset}
24        className="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700"
25      >
26        Try again
27      </button>
28    </div>
29  );
30}

Missing Resource Handling with not-found.js

1// app/products/[productId]/not-found.tsx
2import Link from 'next/link';
3
4export default function ProductNotFound() {
5  return (
6    <div className="container mx-auto px-4 py-16 text-center">
7      <h2 className="text-3xl font-bold mb-4">Product Not Found</h2>
8      <p className="mb-8 text-gray-600">
9        Sorry, we couldn't find the product you're looking for.
10      </p>
11      <Link
12        href="/products"
13        className="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700"
14      >
15        Back to all products
16      </Link>
17    </div>
18  );
19}

Example: Advanced Documentation System

In the Quantum Metropolis, an interactive documentation system allows navigating through complex information hierarchies. Let's implement a similar system in Next.js 15:

1app/
2  ├── docs/
3  │   └── [[...slug]]/     # Optional catch-all
4  │       └── page.tsx     # /docs, /docs/guides, /docs/guides/routing, etc.
1// app/docs/[[...slug]]/page.tsx
2import { fetchDocumentBySlug, fetchDocCategories } from '@/lib/api/documentation';
3import { DocSidebar } from '@/components/docs/DocSidebar';
4import { DocContent } from '@/components/docs/DocContent';
5import { DocBreadcrumbs } from '@/components/docs/DocBreadcrumbs';
6import { DocPagination } from '@/components/docs/DocPagination';
7import { notFound } from 'next/navigation';
8
9export default async function DocPage({ params }: { params: Promise<{ slug?: string[] }> }) {
10  const slug = (await params).slug || [];
11  const slugPath = slug.join('/');
12  
13  // Fetch documentation categories for the sidebar menu
14  const categories = await fetchDocCategories();
15  
16  // If no path is provided, show the main documentation page
17  if (slug.length === 0) {
18    return (
19      <div className="container mx-auto px-4 py-12">
20        <div className="flex flex-col lg:flex-row gap-8">
21          <DocSidebar categories={categories} currentSlug="" />
22          
23          <main className="flex-1">
24            <h1 className="text-4xl font-bold mb-8">Quantum Voyages Documentation</h1>
25            
26            <div className="prose prose-indigo max-w-none">
27              <p>
28                Welcome to the Quantum Voyages documentation center. Here you'll find all the information
29                needed to plan and take a space journey, from basic guides
30                to advanced technical instructions.
31              </p>
32              
33              <h2>Popular Sections</h2>
34              
35              <div className="grid grid-cols-1 md:grid-cols-2 gap-6 not-prose">
36                {categories.map(category => (
37                  <div key={category.slug} className="border rounded-lg p-6 hover:shadow-md transition-shadow">
38                    <h3 className="text-xl font-semibold mb-2">{category.title}</h3>
39                    <p className="text-gray-600 mb-4">{category.description}</p>
40                    <a href={`/docs/\${category.slug}\`} className="text-indigo-600 hover:underline">
41                      Overviewaj {category.title.toLowerCase()}42                    </a>
43                  </div>
44                ))}
45              </div>
46            </div>
47          </main>
48        </div>
49      </div>
50    );
51  }
52  
53  // Fetch document based on the path
54  const document = await fetchDocumentBySlug(slugPath);
55  
56  // If the document doesn't exist, show the 404 page
57  if (!document) {
58    notFound();
59  }
60  
61  // Fetch previous and next documents for navigation
62  const { previousDoc, nextDoc } = await fetchAdjacentDocs(slugPath);
63  
64  return (
65    <div className="container mx-auto px-4 py-12">
66      <DocBreadcrumbs slug={slug} />
67      
68      <div className="flex flex-col lg:flex-row gap-8">
69        <DocSidebar categories={categories} currentSlug={slugPath} />
70        
71        <main className="flex-1">
72          <h1 className="text-4xl font-bold mb-8">{document.title}</h1>
73          
74          <DocContent content={document.content} />
75          
76          <DocPagination previousDoc={previousDoc} nextDoc={nextDoc} />
77        </main>
78      </div>
79    </div>
80  );
81}
82
83// Generating static parameters for popular documentation paths
84export async function generateStaticParams() {
85  const popularDocs = await fetchPopularDocs();
86  
87  return popularDocs.map(doc => ({
88    slug: doc.slug.split('/'),
89  }));
90}

Combining Catch-All Parameters with Other Features

Next.js 15 allows combining catch-all parameters with other advanced routing features:

1. Grouping Routes with Catch-All Parameters

1app/
2  ├── (dashboard)/
3  │   └── admin/
4  │       └── users/
5  │           └── [...roles]/    # /admin/users/admin/editor/etc.
6  │               └── page.tsx

2. Intercepting Routes with Catch-All Parameters

1// app/(shop)/products/[...categories]/(.)[productId]/page.tsx
2// Intercepts /products/category1/category2/productId as a modal
3// during navigation from /products/category1/category2

3. Parallel Routes with Catch-All Parameters

1app/
2  ├── trips/
3  │   ├── page.tsx              # /trips
4  │   ├── @filters/
5  │   │   └── [...params]/      # Parallel route for filters
6  │   │       └── page.tsx
7  │   └── @map/
8  │       └── [...location]/    # Parallel route for map
9  │           └── page.tsx

Advanced Navigation Variants with Parameters

Navigation with Dynamic Parameters

1'use client';
2
3import { useRouter } from 'next/navigation';
4
5export function CategoryNavigator({ categories, subcategories }) {
6  const router = useRouter();
7  
8  const handleCategoryChange = (categoryId) => {
9    const selectedCategory = categories.find(c => c.id === categoryId);
10    router.push(\`/shop/\${selectedCategory.slug}`);
11  };
12  
13  const handleSubcategoryChange = (categoryId, subcategoryId) => {
14    const selectedCategory = categories.find(c => c.id === categoryId);
15    const selectedSubcategory = subcategories.find(sc => sc.id === subcategoryId);
16    router.push(\`/shop/\${selectedCategory.slug}/\${selectedSubcategory.slug}`);
17  };
18  
19  return (
20    <div className="space-y-4">
21      <div>
22        <label className="block mb-2 text-sm font-medium">Category</label>
23        <select 
24          className="w-full p-2 border rounded"
25          onChange={(e) => handleCategoryChange(e.target.value)}
26        >
27          <option value="">Select a category</option>
28          {categories.map(category => (
29            <option key={category.id} value={category.id}>
30              {category.name}
31            </option>
32          ))}
33        </select>
34      </div>
35      
36      <div>
37        <label className="block mb-2 text-sm font-medium">Subcategory</label>
38        <select 
39          className="w-full p-2 border rounded"
40          onChange={(e) => handleSubcategoryChange(selectedCategory, e.target.value)}
41        >
42          <option value="">Select a subcategory</option>
43          {/* Subcategory options */}
44        </select>
45      </div>
46    </div>
47  );
48}

Advanced Search with Catch-All Parameters

1// app/search/[[...query]]/page.tsx
2import { searchProducts } from '@/lib/api/search';
3
4export default async function SearchPage({ params, searchParams }) {
5  const query = (await params).query || [];
6  const searchTerm = query.join(' ');
7  
8  // Fetching query parameters
9  const { category, sort, filters } = await searchParams;
10  
11  // Performing the search
12  const results = await searchProducts({
13    searchTerm,
14    category,
15    sort,
16    filters: filters ? JSON.parse(filters) : undefined
17  });
18  
19  return (
20    <div className="container mx-auto px-4 py-12">
21      <h1 className="text-3xl font-bold mb-8">
22        {searchTerm 
23          ? `Wyniki wyszukiwania dla: "\${searchTerm}"` 
24          : 'Product Search'
25        }
26      </h1>
27      
28      {/* Search filters */}
29      <SearchFilters 
30        initialCategory={category}
31        initialSort={sort}
32        initialFilters={filters ? JSON.parse(filters) : {}}
33      />
34      
35      {/* Search results */}
36      <SearchResults results={results} />
37    </div>
38  );
39}

Summary

Nested dynamic paths and catch-all parameters in Next.js 15 offer an extremely flexible routing system that can handle virtually any navigation scenario in modern web applications:

  1. Nested dynamic parameters enable creating complex hierarchical URL structures, such as

    /destinations/:destinationId/attractions/:attractionId
    .

  2. Catch-all parameters (

    [...param]
    ) allow matching any number of URL segments, which is ideal for documentation systems, product categories, and flexible search interfaces.

  3. Optional catch-all parameters (

    [[...param]]
    ) additionally handle the case of no segments, allowing the use of the same component for the base path and all its subpaths.

  4. Error and missing resource handling through

    error.js
    and
    not-found.js
    files ensures an elegant user experience even when problems occur.

  5. Parameter validation prevents errors and ensures application security.

By combining these techniques with features like static parameter generation (

generateStaticParams
), we can create efficient, dynamic, and easy-to-maintain navigation systems for applications of any scale and complexity.

In the next chapter, we'll explore advanced middleware techniques in Next.js 15, which will allow us to implement features like authentication, access control, and redirects at the routing level.

Go to CodeWorlds