2025-03-02 Web Development, Programming, Technology
Understanding Dynamic Routes in Next.js 15: Async Params and Search Params
By O. Wolfson
Next.js 15 introduces several updates to its App Router, particularly in how dynamic routes handle parameters. One of the most notable changes is that both params
and searchParams
are now asynchronous, requiring developers to handle them as Promise
values. This article will break down a common server component that fetches data from a database and explain how this differs from previous versions.
Supabase is used as an example database.
Dynamic Route Setup
Consider the following Next.js 15 server component inside a dynamic route file located at:
js/app/someroute/[id]/page.tsx
This file handles requests for URLs that include a dynamic segment, such as:
js/someroute/123;
In previous versions of Next.js, dynamic route parameters were passed synchronously as an object. However, in Next.js 15, params
and searchParams
are both asynchronous, meaning they must be awaited before being accessed.
Key Aspects of the Server Component
tsximport React from "react";
import { createClient } from "@/utils/supabase/server";
type Props = {
params: Promise<{ slug: string }>;
searchParams: Promise<{ query: string }>;
};
export default async function Page({ params, searchParams }: Props) {
// Await the params and searchParams objects before accessing their values
const { slug } = await params;
const queryParams = await searchParams;
const supabase = await createClient();
// Fetch data from the database using the dynamic route parameter.
const { data: item, error } = await supabase
.from("items")
.select("*")
.eq("id", slug)
.single();
if (error || !item) {
return (
<div>
<h1>Item not found</h1>
<p>We could not retrieve the requested item.</p>
</div>
);
}
return (
<div className="flex flex-col max-w-3xl w-full gap-0 pt-10">
<div className="prose prose-lg mx-auto min-w-full">
<h1 className="text-4xl font-black capitalize leading-12">
{item.name}
</h1>
</div>
</div>
);
}
Breaking Down the Changes
1. params
and searchParams
Are Now Async
In Next.js 14 and earlier, params
and searchParams
were immediately available as regular objects:
tsxexport default function Page({ params, searchParams }) {
const { slug } = params;
const queryParam = searchParams.query;
}
However, in Next.js 15, these are Promise
values:
tsxexport default async function Page({ params, searchParams }: Props) {
const { slug } = await params;
const queryParams = await searchParams;
}
This means that before accessing their values, you must use await
, treating them like any other asynchronous operation.
2. Why the Change?
By making route parameters asynchronous, Next.js improves flexibility in how these values are resolved. This allows potential integrations with streaming, lazy-loading data, and other optimizations in the App Router.
3. Server Component Handling
This example uses a server component, meaning it runs entirely on the backend. The database fetch (supabase.from("items")
) happens directly on the server, ensuring sensitive data like API credentials never reach the client.
4. Graceful Error Handling
If the requested item does not exist, an error message is displayed. This ensures that broken URLs or missing data don’t crash the app but instead provide a user-friendly response.
Summary
The key difference in Next.js 15 is that route parameters (params
) and query parameters (searchParams
) are now asynchronous. This change enhances Next.js’s ability to handle dynamic data and optimize performance for server components. Developers should be mindful of this update when migrating existing projects, ensuring they properly await
these values before using them.
This shift aligns Next.js with modern JavaScript patterns, where asynchronous data retrieval is more flexible and efficient. While it requires minor code updates, it ultimately improves the developer experience and opens doors for more advanced optimizations in future Next.js versions.