Next.js course Β· Module 2: Routing and Layouts
Dynamic Path Segments and Parameters
In this lesson7
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 16, 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 16, 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 through the params prop. In Next.js 16 params is a Promise, so you read it with const { planetId } = await params.
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: Record<string, {
16 name: string;
17 description: string;
18 gravity: string;
19 diameter: string;
20 dayLength: string;
21 yearLength: string;
22 temperature: string;
23 imageUrl: string;
24 }> = {
25 'mars': {
26 name: 'Mars',
27 description: 'The Red Planet known for harsh conditions and fascinating geological phenomena.',
28 gravity: '3.721 m/sΒ²',
29 diameter: '6,779 km',
30 dayLength: '24h 37m',
31 yearLength: '687 Earth days',
32 temperature: '-63Β°C (average)',
33 imageUrl: '/images/mars.jpg'
34 },
35 'venus': {
36 name: 'Venus',
37 description: 'The second planet from the Sun, known for its extremely hot climate and dense atmosphere.',
38 gravity: '8.87 m/sΒ²',
39 diameter: '12,104 km',
40 dayLength: '243 Earth days',
41 yearLength: '225 Earth days',
42 temperature: '462Β°C (average)',
43 imageUrl: '/images/venus.jpg'
44 },
45 'jupiter': {
46 name: 'Jupiter',
47 description: 'The largest planet in the Solar System, known for the Great Red Spot and numerous moons.',
48 gravity: '24.79 m/sΒ²',
49 diameter: '139,820 km',
50 dayLength: '9h 56m',
51 yearLength: '11.86 Earth years',
52 temperature: '-108Β°C (average)',
53 imageUrl: '/images/jupiter.jpg'
54 }
55 };
56
57 return planets[planetId];
58}
59
60// Component receives params as props
61export default async function PlanetPage({ params }: PlanetPageParams) {
62 // Fetching planet data based on planetId
63 const planet = await getPlanetData((await params).planetId);
64
65 // If the planet doesn't exist, show the 404 page
66 if (!planet) {
67 notFound();
68 }
69
70 return (
71 <div className="container mx-auto px-4 py-12">
72 <h1 className="text-4xl font-bold mb-8">{planet.name}</h1>
73
74 <div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
75 <div className="relative h-96 lg:h-auto rounded-lg overflow-hidden">
76 <Image
77 src={planet.imageUrl}
78 alt={planet.name}
79 fill
80 className="object-cover"
81 preload
82 />
83 </div>
84
85 <div className="space-y-6">
86 <p className="text-lg text-gray-700">{planet.description}</p>
87
88 <div className="bg-gray-50 p-6 rounded-lg shadow-sm">
89 <h2 className="text-2xl font-semibold mb-4">Planetary Data</h2>
90 <table className="w-full">
91 <tbody>
92 <tr className="border-b">
93 <th className="text-left py-2">Gravity</th>
94 <td className="text-right py-2">{planet.gravity}</td>
95 </tr>
96 <tr className="border-b">
97 <th className="text-left py-2">Diameter</th>
98 <td className="text-right py-2">{planet.diameter}</td>
99 </tr>
100 <tr className="border-b">
101 <th className="text-left py-2">Day Length</th>
102 <td className="text-right py-2">{planet.dayLength}</td>
103 </tr>
104 <tr className="border-b">
105 <th className="text-left py-2">Year Length</th>
106 <td className="text-right py-2">{planet.yearLength}</td>
107 </tr>
108 <tr>
109 <th className="text-left py-2">Temperature</th>
110 <td className="text-right py-2">{planet.temperature}</td>
111 </tr>
112 </tbody>
113 </table>
114 </div>
115
116 <div className="flex space-x-4">
117 <a
118 href={`/destinations/${(await params).planetId}/colonies`}
119 className="px-6 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
120 >
121 Colonies
122 </a>
123 <a
124 href={`/destinations/${(await params).planetId}/tours`}
125 className="px-6 py-3 border border-indigo-600 text-indigo-600 rounded-lg hover:bg-indigo-50 transition-colors"
126 >
127 Tours
128 </a>
129 </div>
130 </div>
131 </div>
132 </div>
133 );
134}In the code above:
- We define the
PlanetPageParamstype, which specifies the parameter structure received by the component - The
PlanetPagecomponent receives aparamsobject containingplanetIdfrom the URL - We use this
planetIdto fetch data about the planet - 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 16, 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 /destinationsIn this structure, we have two levels of dynamic segments:
[planetId]- planet identifier[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: Record<string, Record<string, {
16 name: string;
17 description: string;
18 diameter: string;
19 orbitDistance: string;
20 discovered: string;
21 imageUrl: string;
22 }>> = {
23 'mars': {
24 'phobos': {
25 name: 'Phobos',
26 description: "The larger and closer of Mars's two natural satellites, orbiting the planet every 7 hours and 39 minutes.",
27 diameter: '22.2 km',
28 orbitDistance: '9,376 km',
29 discovered: '1877',
30 imageUrl: '/images/phobos.jpg'
31 },
32 'deimos': {
33 name: 'Deimos',
34 description: "The smaller and farther of Mars's two natural satellites, with an irregular shape.",
35 diameter: '12.6 km',
36 orbitDistance: '23,463 km',
37 discovered: '1877',
38 imageUrl: '/images/deimos.jpg'
39 }
40 },
41 'jupiter': {
42 'europa': {
43 name: 'Europa',
44 description: "Jupiter's moon that may harbor a subsurface ocean of water beneath its icy crust.",
45 diameter: '3,121.6 km',
46 orbitDistance: '670,900 km',
47 discovered: '1610',
48 imageUrl: '/images/europa.jpg'
49 },
50 'io': {
51 name: 'Io',
52 description: 'The most volcanically active object in the Solar System, with over 400 active volcanoes.',
53 diameter: '3,643.2 km',
54 orbitDistance: '421,700 km',
55 discovered: '1610',
56 imageUrl: '/images/io.jpg'
57 },
58 'ganymede': {
59 name: 'Ganymede',
60 description: 'The largest moon in the Solar System, even larger than the planet Mercury.',
61 diameter: '5,268.2 km',
62 orbitDistance: '1,070,400 km',
63 discovered: '1610',
64 imageUrl: '/images/ganymede.jpg'
65 }
66 }
67 };
68
69 // Check if the planet and moon exist
70 if (!moonData[planetId] || !moonData[planetId][moonId]) {
71 return null;
72 }
73
74 return {
75 ...moonData[planetId][moonId],
76 planet: planetId
77 };
78}
79
80export default async function MoonPage({ params }: MoonPageParams) {
81 const { planetId, moonId } = await params;
82 const moon = await getMoonData(planetId, moonId);
83
84 if (!moon) {
85 notFound();
86 }
87
88 return (
89 <div className="container mx-auto px-4 py-12">
90 <div className="flex items-center mb-8">
91 <Link
92 href={`/destinations/${planetId}`}
93 className="text-indigo-600 hover:underline flex items-center"
94 >
95 <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
96 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
97 </svg>
98 Back to {planetId.charAt(0).toUpperCase() + planetId.slice(1)}
99 </Link>
100 </div>
101
102 <h1 className="text-4xl font-bold mb-8">{moon.name}</h1>
103
104 <div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
105 <div className="relative h-96 lg:h-auto rounded-lg overflow-hidden">
106 <Image
107 src={moon.imageUrl}
108 alt={moon.name}
109 fill
110 className="object-cover"
111 preload
112 />
113 </div>
114
115 <div className="space-y-6">
116 <p className="text-lg text-gray-700">{moon.description}</p>
117
118 <div className="bg-gray-50 p-6 rounded-lg shadow-sm">
119 <h2 className="text-2xl font-semibold mb-4">Information</h2>
120 <table className="w-full">
121 <tbody>
122 <tr className="border-b">
123 <th className="text-left py-2">Planet</th>
124 <td className="text-right py-2">{moon.planet.charAt(0).toUpperCase() + moon.planet.slice(1)}</td>
125 </tr>
126 <tr className="border-b">
127 <th className="text-left py-2">Diameter</th>
128 <td className="text-right py-2">{moon.diameter}</td>
129 </tr>
130 <tr className="border-b">
131 <th className="text-left py-2">Orbit Distance</th>
132 <td className="text-right py-2">{moon.orbitDistance}</td>
133 </tr>
134 <tr>
135 <th className="text-left py-2">Discovered</th>
136 <td className="text-right py-2">{moon.discovered}</td>
137 </tr>
138 </tbody>
139 </table>
140 </div>
141
142 <div className="flex space-x-4">
143 <a
144 href={`/destinations/${planetId}/${moonId}/research`}
145 className="px-6 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
146 >
147 Scientific Research
148 </a>
149 <a
150 href={`/destinations/${planetId}/${moonId}/landing-sites`}
151 className="px-6 py-3 border border-indigo-600 text-indigo-600 rounded-lg hover:bg-indigo-50 transition-colors"
152 >
153 Landing Sites
154 </a>
155 </div>
156 </div>
157 </div>
158 </div>
159 );
160}
161
162export async function generateStaticParams() {
163 // We generate all possible combinations of planets and their moons
164 return [
165 { planetId: 'mars', moonId: 'phobos' },
166 { planetId: 'mars', moonId: 'deimos' },
167 { planetId: 'jupiter', moonId: 'europa' },
168 { planetId: 'jupiter', moonId: 'io' },
169 { planetId: 'jupiter', moonId: 'ganymede' }
170 ];
171}In this example:
- The component receives two parameters:
planetIdandmoonId - We use both parameters to fetch data about a specific moon of a specific planet
- We implement navigation back to the planet page
- 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 16, 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-objectsImplementation 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 path read with await params 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.tsxImplementation:
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 }: { title: string; href: string }) {
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 16, 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/planetsImplementation 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: Record<string, { id: string; name: string; type: string; habitable: boolean }> = {
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:
- The Route Handler handles requests for a specific planet identified by
id - We implement GET, PUT, and DELETE methods for managing planet data
- The
idparameter is available through theparamsobject 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 text-indigo-600 hover:bg-indigo-50"
84 >
85 Next
86 </a>
87 </div>
88 </div>
89 );
90}In this example:
- The
SearchPagecomponent receivessearchParamsas props, containing all query parameters from the URL - We use these parameters to configure the search
- We implement the
SearchResultscomponent, which fetches data based on the parameters - We use
Suspenseto 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:
- We use
new URL(request.url).searchParamsto get query parameters - We extract and validate specific parameters
- 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 16, we can generate dynamic metadata for each page using the generateMetadata function.
1// app/destinations/[planetId]/page.tsx
2import type { 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:
generateMetadatareceives the sameparamsas the page component- We use
planetIdto fetch planet data - We generate dynamic metadata, including title, description, and Open Graph data
- These metadata are used to optimize the page's SEO
Metadata is collected from the root layout down to the page: Next.js shallowly merges the objects from the successive segments (fields defined in the page override the same fields from the layout), and the result goes into the document's <head>.
Summary
Dynamic path segments and parameters in Next.js 16 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:
- Dynamic segments are created by placing the parameter name in square brackets
[param] - Parameter values are available in page components through the
paramsprop - Catch-all segments (
[...param]) allow matching multiple path segments - Optional catch-all segments (
[[...param]]) work similarly but also match the path without additional segments - Query parameters are available through the
searchParamsprop in page components - 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.
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. Dynamic URL segments in App Router are created using:
2. The loading.js file in App Router is displayed:
3. The error.js file in App Router is used for:
Hands-on tasks in the game
- Code editor
Create the page app/product/[id]/page.js: default-export an async ProductPage({ params }) component. In Next.js 16 params is a Promise, so read id with const { id } = await params and display an h1 heading with the text βProduct {id}β.
- Vertical ordering
Arrange the generateStaticParams function syntax in App Router
- Code editor
Create a loading.js file with a default-exported Loading component that displays the text βLoading...β (you can add a spinner next to it, e.g. a div with the spinner class).