Skip to main content

nextjs-dynamic-routes-params

Guide for Next.js App Router dynamic routes and pathname parameters. Use when building pages that depend on URL segments (IDs, slugs, nested paths), accessing the `params` prop, or fetching resources by identifier. Helps avoid over-nesting by defaulting to the simplest route structure (e.g., `app/[id]` instead of `app/products/[id]` unless the URL calls for it).

Datos de origen

Repositorio
wsimmonds/claude-nextjs-skills
Última actividad en el origen
23 de octubre de 2025 a las 22:42
Idioma detectado de SKILL.md
inglés
Estrellas
108
Forks
22

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
nextjs-dynamic-routes-params
description
Guide for Next.js App Router dynamic routes and pathname parameters. Use when building pages that depend on URL segments (IDs, slugs, nested paths), accessing the `params` prop, or fetching resources by identifier. Helps avoid over-nesting by defaulting to the simplest route structure (e.g., `app/[id]` instead of `app/products/[id]` unless the URL calls for it).
allowed-tools
["Read","Write","Edit","Glob","Grep","Bash"]
# Next.js Dynamic Routes and Pathname Parameters ## When to Use This Skill Use this skill when: - Creating dynamic route segments (e.g., blog/[slug], users/[id]) - Accessing URL pathname parameters in Server or Client Components - Building pages that fetch data based on route parameters - Implementing catch-all or optional catch-all routes - Working with the `params` prop in page.tsx, layout.tsx, or route.ts ## ⚠️ RECOGNIZING WHEN YOU NEED DYNAMIC ROUTES **Look for requirements that tie data to the URL path.** Create a dynamic segment (`[param]`) whenever the UI depends on part of the pathname. Typical signals include: - Details pages that reference “the item’s ID/slug from the URL” - Copy that calls out path segments (e.g., `/products/{id}`, `/blog/{slug}`) - Requirements to fetch data “based on whichever resource is being visited” - Navigation flows where one page links to `/something/{identifier}` **✅ Dynamic route response** ``` Requirement: display product information based on whichever ID appears in the URL Implementation: app/[id]/page.tsx Access parameter with: const { id } = await params; ``` **❌ Static-page response** ``` Implementation: app/page.tsx ← cannot access per-path identifiers ``` **Example requirements that lead to dynamic routes** 1. “Show a product page that loads whichever product ID appears in the URL” → `app/[id]/page.tsx` or `app/products/[id]/page.tsx` 2. “Render a blog article based on its slug” → `app/blog/[slug]/page.tsx` or `app/[slug]/page.tsx` 3. “Support nested docs such as /docs/getting-started/installation” → `app/docs/[...slug]/page.tsx` **Core rule:** If data varies with a URL segment, the folder name needs matching brackets. ## ⚠️ CRITICAL: Avoid Over-Engineering Route Structure **MOST COMMON MISTAKE:** Adding unnecessary nesting to routes. **Default Rule:** When creating a dynamic route, use `app/[id]/page.tsx` or `app/[slug]/page.tsx` unless: - The URL structure is explicitly specified (e.g., "create route at /products/[id]") - You're building multiple resource types that need namespacing - The requirements clearly show a nested URL structure **Do NOT infer nesting from resource names:** - "Fetch a product by ID" → `app/[id]/page.tsx` ✅ (not `app/products/[id]`) - "Show user profile" → `app/[userId]/page.tsx` ✅ (not `app/users/[userId]`) - "Display blog post" → `app/[slug]/page.tsx` ✅ (not `app/blog/[slug]`) **Only nest when explicitly told:** - "Create a route at /blog/[slug]" → `app/blog/[slug]/page.tsx` ✅ - "Products should be at /products/[id]" → `app/products/[id]/page.tsx` ✅ ## Core Concepts ### Dynamic Route Syntax Next.js uses **folder names with square brackets** to create dynamic route segments: ``` app/ ├── [id]/page.tsx # Matches /123, /abc, etc. ├── blog/[slug]/page.tsx # Matches /blog/hello-world ├── shop/[category]/[id]/page.tsx # Matches /shop/electronics/123 └── docs/[...slug]/page.tsx # Matches /docs/a, /docs/a/b, /docs/a/b/c ``` **Key Principle:** The folder structure IS the route structure. ### Route Structure Decision Tree **CRITICAL RULE: Do NOT infer route structure from resource type names!** Just because you're fetching a "product" or "user" doesn't mean you need `/products/[id]` or `/users/[id]`. **Unless explicitly told otherwise, prefer the simplest structure.** **When deciding on route structure:** 1. **Top-level dynamic route** (`app/[id]/page.tsx`) - **DEFAULT CHOICE** - Use this unless specifically told otherwise - Use when the resource IS the primary entity - Use when only ID-based routing is needed - Examples: `/123` for any resource, `/abc-def` for slugs - Pattern: The ID/slug is the only identifier needed - **When in doubt, choose this!** 2. **Nested dynamic route** (`app/category/[id]/page.tsx`) - **ONLY use when explicitly required by the URL structure** - Use when you're told "create a /products/[id] route" - Use when the URL itself needs the category prefix - Examples: `/products/123`, `/blog/my-post` (when specified) - Pattern: Category + identifier (when both are required) 3. **Multi-segment dynamic** (`app/[cat]/[id]/page.tsx`) - Use when hierarchy matters - Examples: `/shop/electronics/123` - Pattern: Multiple levels of categorization **⚠️ COMMON MISTAKE:** Creating `app/products/[id]/page.tsx` when you should create `app/[id]/page.tsx` ❌ **WRONG:** "Fetch a product by ID" → `app/products/[id]/page.tsx` ✅ **CORRECT:** "Fetch a product by ID" → `app/[id]/page.tsx` ❌ **WRONG:** "Create a dynamic route for users" → `app/users/[userId]/page.tsx` ✅ **CORRECT:** "Create a dynamic route for users" → `app/[userId]/page.tsx` **Only add the category prefix when:** - The requirement explicitly says "at /products/..." or similar - You're building multiple resource types that need namespacing - The URL structure is specified in requirements ## Accessing Pathname Parameters ### In Server Components (page.tsx, layout.tsx) **CRITICAL: In Next.js 15+, `params` is a Promise and must be awaited!** ```typescript // ✅ CORRECT - Next.js 15+ export default async function ProductPage({ params, }: { params: Promise<{ id: string }>; }) { const { id } = await params; const product = await fetch(`https://api.example.com/products/${id}`) .then(res => res.json()); return <div>{product.name}</div>; } ``` ```typescript // ❌ WRONG - Treating params as synchronous object (Next.js 15+) export default async function ProductPage({ params, }: { params: { id: string }; // Missing Promise wrapper }) { const product = await fetch(`https://api.example.com/products/${params.id}`); // This will fail because params is a Promise! } ``` **For Next.js 14 and earlier:** ```typescript // Next.js 14 - params is synchronous export default async function ProductPage({ params, }: { params: { id: string }; }) { const product = await fetch(`https://api.example.com/products/${params.id}`) .then(res => res.json()); return <div>{product.name}</div>; } ``` ### In Route Handlers (route.ts) ```typescript // app/api/products/[id]/route.ts export async function GET( request: Request, { params }: { params: Promise<{ id: string }> } ) { const { id } = await params; const product = await db.products.findById(id); return Response.json(product); } ``` ### In Client Components **You CANNOT access `params` directly in Client Components.** Instead: 1. **Use `useParams()` hook:** ```typescript 'use client'; import { useParams } from 'next/navigation'; export function ProductClient() { const params = useParams<{ id: string }>(); const id = params.id; // Use the id... } ``` 2. **Pass params from Server Component:** ```typescript // app/products/[id]/page.tsx (Server Component) export default async function ProductPage({ params, }: { params: Promise<{ id: string }>; }) { const { id } = await params; return <ProductClient productId={id} />; } // components/ProductClient.tsx 'use client'; export function ProductClient({ productId }: { productId: string }) { // Use productId... } ``` ## Common Patterns ### Pattern 1: Simple ID-Based Page ```typescript // app/[id]/page.tsx - Top-level dynamic route interface PageProps { params: Promise<{ id: string }>; } export default async function ItemPage({ params }: PageProps) { const { id } = await params; const item = await fetch(`https://api.example.com/items/${id}`) .then(res => res.json()); return ( <div> <h1>{item.title}</h1> <p>{item.description}</p> </div> ); } ``` ### Pattern 2: Blog Post with Slug ```typescript // app/blog/[slug]/page.tsx interface PageProps { params: Promise<{ slug: string }>; } export default async function BlogPost({ params }: PageProps) { const { slug } = await params; const post = await getPostBySlug(slug); return ( <article> <h1>{post.title}</h1> <div dangerouslySetInnerHTML={{ __html: post.content }} /> </article> ); } // Generate static params for SSG export async function generateStaticParams() { const posts = await getAllPosts(); return posts.map((post) => ({ slug: post.slug, })); } ``` ### Pattern 3: Nested Resources ```typescript // app/users/[userId]/posts/[postId]/page.tsx interface PageProps { params: Promise<{ userId: string; postId: string; }>; } export default async function UserPost({ params }: PageProps) { const { userId, postId } = await params; const [user, post] = await Promise.all([ getUserById(userId), getPostById(postId), ]); return ( <div> <h1>{post.title}</h1> <p>By {user.name}</p> <div>{post.content}</div> </div> ); } ``` ### Pattern 4: Catch-All Routes ```typescript // app/docs/[...slug]/page.tsx - Matches /docs/a, /docs/a/b, /docs/a/b/c interface PageProps { params: Promise<{ slug: string[]; // Array of path segments }>; } export default async function DocsPage({ params }: PageProps) { const { slug } = await params; const path = slug.join('/'); const doc = await getDocByPath(path); return <div>{doc.content}</div>; } ``` ```typescript // app/shop/[[...slug]]/page.tsx - Optional catch-all // Matches /shop, /shop/electronics, /shop/electronics/phones interface PageProps { params: Promise<{ slug?: string[]; // Optional array }>; } export default async function ShopPage({ params }: PageProps) { const { slug = [] } = await params; if (slug.length === 0) { return <ShopHomepage />; } return <CategoryPage category={slug.join('/')} />; } ``` ## TypeScript Best Practices ### Type Safety for Params ```typescript // Define params type separately for reusability type ProductPageParams = { id: string }; interface ProductPageProps { params: Promise<ProductPageParams>; } export default async function ProductPage({ params }: ProductPageProps) { const { id } = await params; // id is typed as string } // Reuse in generateMetadata export async function generateMetadata({ params }: ProductPageProps) { const { id } = await params; const product = await getProduct(id); return { title: product.name }; } ``` ### Multiple Dynamic Segments ```typescript type PostPageParams = { category: string; slug: string; }; interface PostPageProps { params: Promise<PostPageParams>; searchParams: Promise<{ [key: string]: string | string[] | undefined }>; } export default async function PostPage({ params, searchParams }: PostPageProps) { const { category, slug } = await params; const { view } = await searchParams; // All properly typed } ``` ## Common Pitfalls and Solutions ### Pitfall 1: Forgetting params is a Promise (Next.js 15+) ```typescript // ❌ WRONG
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub