We use cookies to enhance your experience on the site
CodeWorlds

Dynamic Path Segments and Parameters

In the Quantum Metropolis, the most advanced transportation system possesses a remarkable ability to adapt to the needs of its citizens. Transport routes are not rigidly defined - they can dynamically reorganize to guide travelers to any location in the city. This flexibility is key to the efficient functioning of such a complex urban organism.

In the world of Next.js 15, dynamic path segments serve an analogous function, allowing you to create routes that adapt to different parameters and contexts. They are what make navigation in an application truly interactive and tailored to the user's needs.

Dynamic Segments in App Router

In Next.js 15, dynamic path segments are created by placing the parameter name in square brackets

[param]
. Such a folder will match different values in that URL segment, and the parameter value will be available inside the page components.

Basic Dynamic Segments

Here is what the file structure looks like for an application with dynamic segments:

1app/
2  ├── destinations/
3  │   ├── page.tsx               # matches /destinations
4  │   └── [planetId]/            # matches /destinations/:planetId (dynamic segment)
5  │       └── page.tsx           # matches /destinations/mars, /destinations/venus, etc.

In this example,

[planetId]
is a dynamic segment that can accept any value, e.g., "mars", "venus", or "jupiter". This value is then available in the page component as
params.planetId
.

Implementing a Page with a Dynamic Segment

Let's look at the implementation of a page that uses the dynamic segment

[planetId]
:

1// app/destinations/[planetId]/page.tsx
2import Image from 'next/image';
3import { notFound } from 'next/navigation';
4
5// This type defines the parameter structure for this page
6type PlanetPageParams = {
7  params: Promise<{
8    planetId: string;
9  }>;
10};
11
12async function getPlanetData(planetId: string) {
13  // Normally we would fetch data from an API or database here
14  // For this example, we use static data
15  const planets = {
16    'mars': {
17      name: 'Mars',
18      description: 'The Red Planet known for harsh conditions and fascinating geological phenomena.',
19      gravity: '3.721 m/s²',
20      diameter: '6,779 km',
21      dayLength: '24h 37m',
22      yearLength: '687 Earth days',
23      temperature: '-63°C (average)',
24      imageUrl: '/images/mars.jpg'
25    },
26    'venus': {
27      name: 'Venus',
28      description: 'The second planet from the Sun, known for its extremely hot climate and dense atmosphere.',
29      gravity: '8.87 m/s²',
30      diameter: '12,104 km',
31      dayLength: '243 Earth days',
32      yearLength: '225 Earth days',
33      temperature: '462°C (average)',
34      imageUrl: '/images/venus.jpg'
35    },
36    'jupiter': {
37      name: 'Jupiter',
38      description: 'The largest planet in the Solar System, known for the Great Red Spot and numerous moons.',
39      gravity: '24.79 m/s²',
40      diameter: '139,820 km',
41      dayLength: '9h 56m',
42      yearLength: '11.86 Earth years',
43      temperature: '-108°C (average)',
44      imageUrl: '/images/jupiter.jpg'
45    }
46  };
47
48  return planets[planetId];
49}
50
51// Component receives params as props
52export default async function PlanetPage({ params }: PlanetPageParams) {
53  // Fetching planet data based on planetId
54  const planet = await getPlanetData((await params).planetId);
55  
56  // If the planet doesn't exist, show the 404 page
57  if (!planet) {
58    notFound();
59  }
60  
61  return (
62    <div className="container mx-auto px-4 py-12">
63      <h1 className="text-4xl font-bold mb-8">{planet.name}</h1>
64      
65      <div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
66        <div className="relative h-96 lg:h-auto rounded-lg overflow-hidden">
67          <Image
68            src={planet.imageUrl}
69            alt={planet.name}
70            fill
71            className="object-cover"
72            priority
73          />
74        </div>
75        
76        <div className="space-y-6">
77          <p className="text-lg text-gray-700">{planet.description}</p>
78          
79          <div className="bg-gray-50 p-6 rounded-lg shadow-sm">
80            <h2 className="text-2xl font-semibold mb-4">Planetary Data</h2>
81            <table className="w-full">
82              <tbody>
83                <tr className="border-b">
84                  <th className="text-left py-2">Gravity</th>
85                  <td className="text-right py-2">{planet.gravity}</td>
86                </tr>
87                <tr className="border-b">
88                  <th className="text-left py-2">Diameter</th>
89                  <td className="text-right py-2">{planet.diameter}</td>
90                </tr>
91                <tr className="border-b">
92                  <th className="text-left py-2">Day Length</th>
93                  <td className="text-right py-2">{planet.dayLength}</td>
94                </tr>
95                <tr className="border-b">
96                  <th className="text-left py-2">Year Length</th>
97                  <td className="text-right py-2">{planet.yearLength}</td>
98                </tr>
99                <tr>
100                  <th className="text-left py-2">Temperature</th>
101                  <td className="text-right py-2">{planet.temperature}</td>
102                </tr>
103              </tbody>
104            </table>
105          </div>
106          
107          <div className="flex space-x-4">
108            <a 
109              href={`/destinations/\${(await params).planetId}/colonies\`}
110              className="px-6 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
111            >
112              Colonies
113            </a>
114            <a 
115              href={`/destinations/\${(await params).planetId}/tours\`}
116              className="px-6 py-3 border border-indigo-600 text-indigo-600 rounded-lg hover:bg-indigo-50 transition-colors"
117            >
118              Tours
119            </a>
120          </div>
121        </div>
122      </div>
123    </div>
124  );
125}

In the code above:

  1. We define the
    PlanetPageParams
    type, which specifies the parameter structure received by the component
  2. The
    PlanetPage
    component receives a
    params
    object containing
    planetId
    from the URL
  3. We use this
    planetId
    to fetch data about the planet
  4. We display detailed information about the planet, as well as links to subpages that also use the same parameter

Generating Static Pages for Known Parameters

If we know in advance all possible values for a dynamic segment, we can generate static pages during the build process, which significantly improves performance. The

generateStaticParams
function serves this purpose:

1// app/destinations/[planetId]/page.tsx
2
3// This function defines the static paths that will be generated during the build
4export async function generateStaticParams() {
5  // Normally we would fetch data from an API or database here
6  // We return an array of objects with parameters to be statically generated
7  return [
8    { planetId: 'mars' },
9    { planetId: 'venus' },
10    { planetId: 'jupiter' }
11  ];
12}
13
14// Rest of the component...

Thanks to this, Next.js will generate three static pages during the build: for Mars, Venus, and Jupiter. This will significantly speed up their loading, as they won't need to be dynamically generated with each request.

Multi-Level Dynamic Segments

In the Quantum Metropolis, the transportation system enables traveling to any destination through a series of precise transfers, each narrowing the space to the selected target. Similarly in Next.js 15, we can create multi-level dynamic segments to precisely specify the requested resources.

Structure with Multiple Dynamic Segments

1app/
2  ├── destinations/
3  │   ├── [planetId]/           # matches /destinations/:planetId
4  │   │   ├── page.tsx          # matches /destinations/mars
5  │   │   └── [moonId]/         # matches /destinations/:planetId/:moonId
6  │   │       └── page.tsx      # matches /destinations/mars/phobos
7  │   └── page.tsx              # matches /destinations

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

  1. [planetId]
    - planet identifier
  2. [moonId]
    - moon identifier for the given planet

Implementing a Page with Nested Parameters

1// app/destinations/[planetId]/[moonId]/page.tsx
2import Image from 'next/image';
3import Link from 'next/link';
4import { notFound } from 'next/navigation';
5
6type MoonPageParams = {
7  params: Promise<{
8    planetId: string;
9    moonId: string;
10  }>;
11};
12
13async function getMoonData(planetId: string, moonId: string) {
14  // Moon-to-planet mapping system
15  const moonData = {
16    'mars': {
17      'phobos': {
18        name: 'Phobos',
19        description: 'The larger and closer of Mars's two natural satellites, orbiting the planet every 7 hours and 39 minutes.',
20        diameter: '22.2 km',
21        orbitDistance: '9,376 km',
22        discovered: '1877',
23        imageUrl: '/images/phobos.jpg'
24      },
25      'deimos': {
26        name: 'Deimos',
27        description: 'The smaller and farther of Mars's two natural satellites, with an irregular shape.',
28        diameter: '12.6 km',
29        orbitDistance: '23,463 km',
30        discovered: '1877',
31        imageUrl: '/images/deimos.jpg'
32      }
33    },
34    'jupiter': {
35      'europa': {
36        name: 'Europa',
37        description: 'Jupiter's moon that may harbor a subsurface ocean of water beneath its icy crust.',
38        diameter: '3,121.6 km',
39        orbitDistance: '670,900 km',
40        discovered: '1610',
41        imageUrl: '/images/europa.jpg'
42      },
43      'io': {
44        name: 'Io',
45        description: 'The most volcanically active object in the Solar System, with over 400 active volcanoes.',
46        diameter: '3,643.2 km',
47        orbitDistance: '421,700 km',
48        discovered: '1610',
49        imageUrl: '/images/io.jpg'
50      },
51      'ganymede': {
52        name: 'Ganymede',
53        description: 'The largest moon in the Solar System, even larger than the planet Mercury.',
54        diameter: '5,268.2 km',
55        orbitDistance: '1,070,400 km',
56        discovered: '1610',
57        imageUrl: '/images/ganymede.jpg'
58      }
59    }
60  };
61  
62  // Check if the planet and moon exist
63  if (!moonData[planetId] || !moonData[planetId][moonId]) {
64    return null;
65  }
66  
67  return {
68    ...moonData[planetId][moonId],
69    planet: planetId
70  };
71}
72
73export default async function MoonPage({ params }: MoonPageParams) {
74  const { planetId, moonId } = await params;
75  const moon = await getMoonData(planetId, moonId);
76  
77  if (!moon) {
78    notFound();
79  }
80  
81  return (
82    <div className="container mx-auto px-4 py-12">
83      <div className="flex items-center mb-8">
84        <Link 
85          href={`/destinations/\${planetId}\`}
86          className="text-indigo-600 hover:underline flex items-center"
87        >
88          <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
89            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
90          </svg>
91          Back to {planetId.charAt(0).toUpperCase() + planetId.slice(1)}
92        </Link>
93      </div>
94
95      <h1 className="text-4xl font-bold mb-8">{moon.name}</h1>
96      
97      <div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
98        <div className="relative h-96 lg:h-auto rounded-lg overflow-hidden">
99          <Image
100            src={moon.imageUrl}
101            alt={moon.name}
102            fill
103            className="object-cover"
104            priority
105          />
106        </div>
107        
108        <div className="space-y-6">
109          <p className="text-lg text-gray-700">{moon.description}</p>
110          
111          <div className="bg-gray-50 p-6 rounded-lg shadow-sm">
112            <h2 className="text-2xl font-semibold mb-4">Information</h2>
113            <table className="w-full">
114              <tbody>
115                <tr className="border-b">
116                  <th className="text-left py-2">Planet</th>
117                  <td className="text-right py-2">{moon.planet.charAt(0).toUpperCase() + moon.planet.slice(1)}</td>
118                </tr>
119                <tr className="border-b">
120                  <th className="text-left py-2">Diameter</th>
121                  <td className="text-right py-2">{moon.diameter}</td>
122                </tr>
123                <tr className="border-b">
124                  <th className="text-left py-2">Orbit Distance</th>
125                  <td className="text-right py-2">{moon.orbitDistance}</td>
126                </tr>
127                <tr>
128                  <th className="text-left py-2">Discovered</th>
129                  <td className="text-right py-2">{moon.discovered}</td>
130                </tr>
131              </tbody>
132            </table>
133          </div>
134
135          <div className="flex space-x-4">
136            <a 
137              href={`/destinations/\${planetId}/\${moonId}/research\`}
138              className="px-6 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
139            >
140              Scientific Research
141            </a>
142            <a 
143              href={`/destinations/\${planetId}/\${moonId}/landing-sites\`}
144              className="px-6 py-3 border border-indigo-600 text-indigo-600 rounded-lg hover:bg-indigo-50 transition-colors"
145            >
146              Landing Sites
147            </a>
148          </div>
149        </div>
150      </div>
151    </div>
152  );
153}
154
155export async function generateStaticParams() {
156  // We generate all possible combinations of planets and their moons
157  return [
158    { planetId: 'mars', moonId: 'phobos' },
159    { planetId: 'mars', moonId: 'deimos' },
160    { planetId: 'jupiter', moonId: 'europa' },
161    { planetId: 'jupiter', moonId: 'io' },
162    { planetId: 'jupiter', moonId: 'ganymede' }
163  ];
164}

In this example:

  1. The component receives two parameters:
    planetId
    and
    moonId
  2. We use both parameters to fetch data about a specific moon of a specific planet
  3. We implement navigation back to the planet page
  4. We statically generate all known combinations of planets and moons

Catch-All and Optional Catch-All Parameters

In the Quantum Metropolis, some transport lines are so flexible that they can reach any number of points along the route, collecting all relevant route data as travel parameters. In Next.js 15, the equivalents are catch-all and optional catch-all segments.

Catch-All Segments ([...param])

A catch-all segment, denoted as

[...param]
, matches a path with any number of segments. All matched segments are passed as an array.

1app/
2  ├── stellar-objects/
3  │   ├── [...path]/           # matches /stellar-objects/any/number/of/segments
4  │   │   └── page.tsx         
5  │   └── page.tsx              # matches /stellar-objects

Implementation of a page with a catch-all segment:

1// app/stellar-objects/[...path]/page.tsx
2type CatchAllParams = {
3  params: Promise<{
4    path: string[];
5  }>;
6};
7
8export default async function StellarObjectsPage({ params }: CatchAllParams) {
9  const { path } = await params;
10
11  return (
12    <div className="container mx-auto px-4 py-12">
13      <h1 className="text-4xl font-bold mb-8">Stellar Objects Browser</h1>
14      
15      <div className="bg-gray-100 p-4 rounded mb-8">
16        <p className="font-mono">Path: /{path.join('/')}</p>
17        <p>Segments: {path.length}</p>
18        <ul className="list-disc pl-6 mt-2">
19          {path.map((segment, index) => (
20            <li key={index}>Segment {index + 1}: {segment}</li>
21          ))}
22        </ul>
23      </div>
24      
25      {/* Normal page content here... */}
26    </div>
27  );
28}

Example URL:

/stellar-objects/stars/main-sequence/g-type
will be matched to this page, and
params.path
will be the array
["stars", "main-sequence", "g-type"]
.

Optional Catch-All Segments ([...param])

An optional catch-all segment, denoted as

[[...param]]
(double square brackets), works similarly to the standard catch-all, but also matches the path without any additional segments.

1app/
2  ├── knowledge-base/
3      ├── [[...topic]]/         # matches /knowledge-base, /knowledge-base/planets, /knowledge-base/planets/terrestrial, etc.
4          └── page.tsx

Implementation:

1// app/knowledge-base/[[...topic]]/page.tsx
2type OptionalCatchAllParams = {
3  params: Promise<{
4    topic?: string[];
5  }>;
6};
7
8export default async function KnowledgeBasePage({ params }: OptionalCatchAllParams) {
9  const { topic } = await params;
10  
11  // If topic doesn't exist, we're on the main knowledge base page
12  const isRoot = !topic || topic.length === 0;
13  
14  return (
15    <div className="container mx-auto px-4 py-12">
16      <h1 className="text-4xl font-bold mb-8">
17        {isRoot ? 'Space Knowledge Base' : 'Topic: ' + topic.join(' > ')}
18      </h1>
19      
20      {isRoot ? (
21        <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
22          <TopicCard title="Planets" href="/knowledge-base/planets" />
23          <TopicCard title="Stars" href="/knowledge-base/stars" />
24          <TopicCard title="Galaxies" href="/knowledge-base/galaxies" />
25          {/* More topic cards... */}
26        </div>
27      ) : (
28        <div>
29          <div className="bg-gray-100 p-4 rounded mb-8">
30            <p>You are browsing topic: <strong>{topic.join(' > ')}</strong></p>
31            
32            <div className="mt-4">
33              <a href="/knowledge-base" className="text-indigo-600 hover:underline">
34                Return to the main knowledge base page
35              </a>
36            </div>
37          </div>
38          
39          {/* Specific topic content... */}
40        </div>
41      )}
42    </div>
43  );
44}
45
46function TopicCard({ title, href }) {
47  return (
48    <a href={href} className="block p-6 bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow">
49      <h2 className="text-xl font-semibold mb-2">{title}</h2>
50      <p className="text-gray-600">Discover knowledge about {title.toLowerCase()}.</p>
51    </a>
52  );
53}

This page will match both

/knowledge-base
(main knowledge base page) and
/knowledge-base/planets/terrestrial
(specific topic).

Parameters in Route Handlers

In the Quantum Metropolis, not only citizen transport lines use route parameters - automated systems also communicate with each other through dynamically configured channels. In Next.js 15, the equivalent is Route Handlers, which can also use dynamic segments.

Dynamic Segments in API Routes

1app/
2  ├── api/
3      ├── planets/
4          ├── [id]/
5          │   └── route.ts     # matches /api/planets/:id
6          └── route.ts         # matches /api/planets

Implementation of a Route Handler with a dynamic segment:

1// app/api/planets/[id]/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3
4// Type for parameters
5type PlanetRouteContext = {
6  params: Promise<{
7    id: string;
8  }>;
9};
10
11// Sample data
12const planetData = {
13  'mars': { 
14    id: 'mars',
15    name: 'Mars',
16    type: 'terrestrial',
17    habitable: false
18  },
19  'earth': {
20    id: 'earth',
21    name: 'Earth',
22    type: 'terrestrial',
23    habitable: true
24  },
25  'jupiter': {
26    id: 'jupiter',
27    name: 'Jupiter',
28    type: 'gas-giant',
29    habitable: false
30  }
31};
32
33export async function GET(
34  request: NextRequest,
35  { params }: PlanetRouteContext
36) {
37  const { id } = await params;
38  
39  // Check if the planet exists
40  if (!planetData[id]) {
41    return NextResponse.json(
42      { error: 'Planet not found' },
43      { status: 404 }
44    );
45  }
46  
47  // Return planet data
48  return NextResponse.json(planetData[id]);
49}
50
51export async function PUT(
52  request: NextRequest,
53  { params }: PlanetRouteContext
54) {
55  const { id } = await params;
56  
57  // Check if the planet exists
58  if (!planetData[id]) {
59    return NextResponse.json(
60      { error: 'Planet not found' },
61      { status: 404 }
62    );
63  }
64  
65  try {
66    // Parse data from the request
67    const updateData = await request.json();
68    
69    // Update planet data (simulation only)
70    // In a real application, we would save this to a database
71    const updatedPlanet = {
72      ...planetData[id],
73      ...updateData
74    };
75    
76    // Return the updated data
77    return NextResponse.json(updatedPlanet);
78  } catch (error) {
79    return NextResponse.json(
80      { error: 'Invalid data' },
81      { status: 400 }
82    );
83  }
84}
85
86export async function DELETE(
87  request: NextRequest,
88  { params }: PlanetRouteContext
89) {
90  const { id } = await params;
91  
92  // Check if the planet exists
93  if (!planetData[id]) {
94    return NextResponse.json(
95      { error: 'Planet not found' },
96      { status: 404 }
97    );
98  }
99  
100  // Delete the planet (simulation only)
101  // In a real application, we would delete it from the database
102  
103  return NextResponse.json({ success: true });
104}

In this example:

  1. The Route Handler handles requests for a specific planet identified by
    id
  2. We implement GET, PUT, and DELETE methods for managing planet data
  3. The
    id
    parameter is available through the
    params
    object passed to the handler functions

Retrieving Query Parameters

In the Quantum Metropolis, beyond the main routes, the transportation system can be additionally configured through optional parameters such as "expressFlight" or "comfortLevel". In the world of Next.js, these optional settings are represented by query parameters.

Accessing Query Parameters in Server Components

1// app/search/page.tsx
2import { Suspense } from 'react';
3
4// This is a server component
5export default async function SearchPage({
6  searchParams
7}: {
8  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
9}) {
10  // Get query parameters
11  const query = (await searchParams).q || '';
12  const category = (await searchParams).category || 'all';
13  const page = Number((await searchParams).page) || 1;
14  
15  return (
16    <div className="container mx-auto px-4 py-12">
17      <h1 className="text-4xl font-bold mb-8">Space Search Engine</h1>
18      
19      <div className="mb-8">
20        <p>Search: <strong>{query}</strong></p>
21        <p>Category: <strong>{category}</strong></p>
22        <p>Page: <strong>{page}</strong></p>
23      </div>
24      
25      <Suspense fallback={<div>Loading results...</div>}>
26        <SearchResults query={query} category={category} page={page} />
27      </Suspense>
28    </div>
29  );
30}
31
32// This component fetches and displays results
33async function SearchResults({ 
34  query, 
35  category, 
36  page 
37}: { 
38  query: string | string[], 
39  category: string | string[], 
40  page: number 
41}) {
42  // Normally we would fetch data based on the parameters
43  // Simulating delay for Suspense demonstration
44  await new Promise(resolve => setTimeout(resolve, 1000));
45  
46  // If there's no query, display a message
47  if (!query) {
48    return (
49      <div className="bg-gray-100 p-8 rounded-lg text-center">
50        <p className="text-lg">Enter a phrase to start searching.</p>
51      </div>
52    );
53  }
54  
55  // Normally we would display search results here
56  return (
57    <div className="space-y-6">
58      <div className="p-6 bg-white rounded-lg shadow">
59        <h2 className="text-xl font-semibold mb-2">Result 1</h2>
60        <p>Found for: {query}</p>
61      </div>
62      <div className="p-6 bg-white rounded-lg shadow">
63        <h2 className="text-xl font-semibold mb-2">Result 2</h2>
64        <p>Found for: {query}</p>
65      </div>
66      <div className="p-6 bg-white rounded-lg shadow">
67        <h2 className="text-xl font-semibold mb-2">Result 3</h2>
68        <p>Found for: {query}</p>
69      </div>
70      
71      <div className="flex justify-center space-x-2 mt-8">
72        <a 
73          href={`/search?q=\${query}&category=\${category}&page=\${Math.max(1, page - 1)}\`}
74          className={`px-4 py-2 border rounded \${page === 1 ? 'text-gray-400 cursor-not-allowed' : 'text-indigo-600 hover:bg-indigo-50'}\`}
75        >
76          Previous
77        </a>
78        <span className="px-4 py-2 bg-indigo-600 text-white rounded">
79          {page}
80        </span>
81        <a 
82          href={`/search?q=\${query}&category=\${category}&page=\${page + 1}\`}
83          className={`px-4 py-2 border rounded \${page === 1 ? 'text-gray-400 cursor-not-allowed' : 'text-indigo-600 hover:bg-indigo-50'}\`}
84        >
85          Next
86        </a>
87      </div>
88    </div>
89  );
90}

In this example:

  1. The
    SearchPage
    component receives
    searchParams
    as props, containing all query parameters from the URL
  2. We use these parameters to configure the search
  3. We implement the
    SearchResults
    component, which fetches data based on the parameters
  4. We use
    Suspense
    to handle the loading state

Accessing Query Parameters in Route Handlers

1// app/api/search/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3
4export async function GET(request: NextRequest) {
5  // Get URL from the request
6  const { searchParams } = new URL(request.url);
7  
8  // Get query parameters
9  const query = searchParams.get('q');
10  const category = searchParams.get('category') || 'all';
11  const page = Number(searchParams.get('page')) || 1;
12  const limit = Number(searchParams.get('limit')) || 10;
13  
14  // Check if query exists
15  if (!query) {
16    return NextResponse.json(
17      { error: 'The q (query) parameter is required' },
18      { status: 400 }
19    );
20  }
21  
22  // Here we would normally fetch data from a database
23  // For the sake of this example, we return simulated data
24  const results = Array.from({ length: limit }, (_, i) => ({
25    id: `result-\${(page - 1) * limit + i + 1}`,
26    title: `Wynik \${(page - 1) * limit + i + 1} dla "\${query}"`,
27    category,
28    relevance: Math.round(Math.random() * 100)
29  }));
30  
31  return NextResponse.json({
32    query,
33    category,
34    page,
35    limit,
36    totalResults: 100, // Simulated total number of results
37    results
38  });
39}

In this example:

  1. We use
    new URL(request.url).searchParams
    to get query parameters
  2. We extract and validate specific parameters
  3. We return data appropriate for the given parameters

Generating Dynamic Metadata

In the Quantum Metropolis, every transport line has dynamically generated route information available through the city's information system. In Next.js 15, we can generate dynamic metadata for each page using the

generateMetadata
function.

1// app/destinations/[planetId]/page.tsx
2import { Metadata } from 'next';
3
4// Type for parameters
5type PlanetParams = {
6  params: Promise<{
7    planetId: string;
8  }>;
9};
10
11// Function generating metadata based on planetId
12export async function generateMetadata(
13  { params }: PlanetParams
14): Promise<Metadata> {
15  const { planetId } = await params;
16  
17  // Fetching planet data
18  const planet = await getPlanetData(planetId);
19  
20  // If the planet doesn't exist, return default metadata
21  if (!planet) {
22    return {
23      title: 'Planet not found | Quantum Voyages',
24      description: 'No information found about the requested planet.'
25    };
26  }
27  
28  // Return planet-specific metadata
29  return {
30    title: `\${planet.name} | Quantum Voyages`,
31    description: planet.description.substring(0, 160), // Limit to 160 characters for SEO
32    openGraph: {
33      title: `Odkryj \${planet.name} z Quantum Voyages`,
34      description: planet.description.substring(0, 160),
35      images: [
36        {
37          url: `https://example.com\${planet.imageUrl}`,
38          width: 1200,
39          height: 630,
40          alt: planet.name
41        }
42      ]
43    }
44  };
45}
46
47// Rest of the component code...

In this example:

  1. generateMetadata
    receives the same
    params
    as the page component
  2. We use
    planetId
    to fetch planet data
  3. We generate dynamic metadata, including title, description, and Open Graph data
  4. These metadata are used to optimize the page's SEO

Summary

Dynamic path segments and parameters in Next.js 15 are powerful tools that enable the creation of flexible and interactive applications. Just as in the Quantum Metropolis, where the transportation system adapts to the needs of its citizens, dynamic routing in Next.js adapts to user needs.

Key points to remember:

  1. Dynamic segments are created by placing the parameter name in square brackets
    [param]
  2. Parameter values are available in page components through the
    params
    prop
  3. Catch-all segments (
    [...param]
    ) allow matching multiple path segments
  4. Optional catch-all segments (
    [[...param]]
    ) work similarly but also match the path without additional segments
  5. Query parameters are available through the
    searchParams
    prop in page components
  6. Dynamic metadata can be generated based on path parameters

With these features, you can build advanced applications with intuitive navigation and rich functionality tailored to specific user needs.

In the next chapter, we'll discover how to efficiently navigate between different pages in our application using the

Link
component and the
useRouter
hook.

Go to CodeWorlds