Dynamic routes in Next.js 15 — params, catch-all, and examples
Learn how dynamic routes work in Next.js 15 with the App Router. Understand [slug], [...params], params as Promises, and see code examples for blogs, profiles, and dashboards.
What are dynamic routes?
Dynamic routes let one page handle many URLs. Instead of creating app/blog/article-1/page.tsx, app/blog/article-2/page.tsx, etc., you create one file: app/blog/[slug]/page.tsx. The [slug] part captures whatever is in the URL.
Step 1: Basic dynamic route
Create app/blog/[slug]/page.tsx:
// app/blog/[slug]/page.tsx
export default async function BlogPost({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return <h1>Blog post: {slug}</h1>;
}Important for Next.js 15: params is now a Promise. You must use await params. In Next.js 14 it was a plain object — this is one of the biggest breaking changes.
Step 2: Generate metadata dynamically
// Same file — add generateMetadata
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
// Look up the article data
const article = articles.find((a) => a.slug === slug);
return {
title: article?.title ?? "Blog",
description: article?.description,
};
}This gives each blog post its own title and description in Google search results, which is crucial for SEO.
Step 3: Catch-all routes
Use [...slug] to match multiple path segments:
// app/docs/[...slug]/page.tsx
// Matches: /docs/getting-started
// /docs/api/authentication
// /docs/api/payments/stripe
export default async function DocsPage({
params,
}: {
params: Promise<{ slug: string[] }>;
}) {
const { slug } = await params;
// slug is an array: ["api", "payments", "stripe"]
return <h1>Docs: {slug.join(" / ")}</h1>;
}Step 4: Real-world example
Here's how a user profile page looks in practice:
// app/user/[id]/page.tsx
import { createClient } from "@/libs/supabase/server";
import { notFound } from "next/navigation";
export default async function UserProfile({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const supabase = await createClient();
const { data: profile } = await supabase
.from("profiles")
.select("full_name, bio")
.eq("id", id)
.single();
if (!profile) {
notFound(); // Shows 404 page
}
return (
<div>
<h1>{profile.full_name}</h1>
<p>{profile.bio}</p>
</div>
);
}Routes already structured
Delfy uses dynamic routes for blog posts, docs, profiles, and more — all following Next.js 15 patterns with proper params handling and metadata.
See what's included