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.
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/:attractionIdIn this structure, we have two levels of dynamic segments:
[destinationId] and [attractionId]. This allows creating paths such as /destinations/mars/attractions/olympus-mons.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}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 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.
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 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}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}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'])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}In Next.js 15, we can access path parameters in several ways:
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-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}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}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}Next.js 15 enables elegant handling of situations when a resource is not found or an error occurs:
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}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}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}Next.js 15 allows combining catch-all parameters with other advanced routing features:
1app/
2 ├── (dashboard)/
3 │ └── admin/
4 │ └── users/
5 │ └── [...roles]/ # /admin/users/admin/editor/etc.
6 │ └── page.tsx1// app/(shop)/products/[...categories]/(.)[productId]/page.tsx
2// Intercepts /products/category1/category2/productId as a modal
3// during navigation from /products/category1/category21app/
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.tsx1'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}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}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:
Nested dynamic parameters enable creating complex hierarchical URL structures, such as
/destinations/:destinationId/attractions/:attractionId.Catch-all parameters (
[...param]) allow matching any number of URL segments, which is ideal for documentation systems, product categories, and flexible search interfaces.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.Error and missing resource handling through
error.js and not-found.js files ensures an elegant user experience even when problems occur.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.