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).
What this skill does
# 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>
);
}
/Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.