home / skills / wsimmonds / claude-nextjs-skills / nextjs-pathname-id-fetch

nextjs-pathname-id-fetch skill

/nextjs-pathname-id-fetch

This skill explains and implements the Next.js pathname ID fetch pattern to load data from URL parameters in server components.

npx playbooks add skill wsimmonds/claude-nextjs-skills --skill nextjs-pathname-id-fetch

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
6.8 KB
---
name: nextjs-pathname-id-fetch
description: Focused pattern for fetching data using URL parameters in Next.js. Covers creating dynamic routes ([id], [slug]) and accessing route parameters in server components to fetch data from APIs. Use when building pages that display individual items (product pages, blog posts, user profiles) based on a URL parameter. Complements nextjs-dynamic-routes-params with a simplified, common-case pattern.
allowed-tools: Read, Write, Edit, Glob, Grep, Bash
---

# Next.js: Pathname ID Fetch Pattern

## When This Pattern Applies

Use this pattern whenever a page needs to load data based on whatever identifier appears in the URL. Common scenarios include:
- Detail pages for products, posts, or users (`/products/{id}`, `/blog/{slug}`)
- Admin dashboards that drill into a selected resource (`/admin/orders/{orderId}`)
- Documentation or knowledge bases with nested paths (`/docs/getting-started/installation`)

If the requirement says the data should change depending on the current URL path, the route segment must be dynamic (e.g., `[id]`, `[slug]`, `[...slug]`).

## The Pattern

**✅ Recommended implementation**
```
1. Create a dynamic folder: app/[id]/page.tsx
2. Access the parameter: const { id } = await params;
3. Fetch data using that identifier
4. Render the requested information
```

**❌ Pitfall**
```
Using app/page.tsx for this scenario prevents access to per-path identifiers.
```

## Complete Implementation Example

```typescript
// app/[id]/page.tsx

// IMPORTANT: Server component (NO 'use client' needed!)
export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  // Next.js 15+: params must be awaited
  const { id } = await params;

  // Fetch data using the ID from the URL
  const response = await fetch(`https://api.example.com/products/${id}`);
  const product = await response.json();

  // Return JSX with the fetched data
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>Price: ${product.price}</p>
    </div>
  );
}
```

## File Structure

```
app/
└── [id]/                 ← Dynamic route folder with brackets
    └── page.tsx          ← Server component page
```

**URL Mapping:**
- `/123` → params = `{ id: '123' }`
- `/abc` → params = `{ id: 'abc' }`
- `/product-xyz` → params = `{ id: 'product-xyz' }`

## Key Rules

### 1. Folder Name MUST Use Brackets
```
✅ app/[id]/page.tsx
✅ app/[productId]/page.tsx
✅ app/[slug]/page.tsx
❌ app/id/page.tsx        (no brackets = static route)
❌ app/page.tsx            (can't access params here)
```

### 2. This is a Server Component (Default)
```typescript
// ✅ CORRECT - No 'use client' needed
export default async function Page({ params }) {
  const { id } = await params;
  const data = await fetch(`/api/${id}`);
  return <div>{data.name}</div>;
}

// ❌ WRONG - Don't add 'use client' for server components
'use client';  // ← Remove this!
export default async function Page({ params }) { ... }
```

### 3. Params Must Be Awaited (Next.js 15+)
```typescript
// ✅ CORRECT - Next.js 15+
export default async function Page({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;  // Must await
  // ...
}

// ⚠️ OLD (Next.js 14 and earlier - deprecated)
export default async function Page({
  params,
}: {
  params: { id: string };
}) {
  const { id } = params;  // No await needed in old versions
  // ...
}
```

### 4. Keep It Simple - Don't Over-Nest
```
✅ app/[id]/page.tsx              (simple, clean)
❌ app/products/[id]/page.tsx     (only if explicitly required!)
```

Unless requirements explicitly call for `/products/[id]`, keep the structure at the top level (`app/[id]/page.tsx`).

## TypeScript: NEVER Use `any` Type

This codebase has `@typescript-eslint/no-explicit-any` enabled. Using `any` will cause build failures.

```typescript
// ❌ WRONG
function processProduct(product: any) { ... }

// ✅ CORRECT - Define proper types
interface Product {
  id: string;
  name: string;
  price: number;
}

function processProduct(product: Product) { ... }

// ✅ ALSO CORRECT - Use unknown if type truly unknown
function processData(data: unknown) {
  // Type guard required before using
  if (typeof data === 'object' && data !== null) {
    // ...
  }
}
```

## Common Variations

### Different Parameter Names
```typescript
// app/[productId]/page.tsx
export default async function Page({
  params,
}: {
  params: Promise<{ productId: string }>;
}) {
  const { productId } = await params;
  // ...
}

// app/[slug]/page.tsx
export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  // ...
}
```

### Multiple Parameters
```typescript
// app/[category]/[id]/page.tsx
export default async function Page({
  params,
}: {
  params: Promise<{ category: string; id: string }>;
}) {
  const { category, id } = await params;
  const data = await fetch(`/api/${category}/${id}`);
  // ...
}
```

## Complete Working Example

```typescript
// app/[id]/page.tsx - Product detail page

interface Product {
  id: string;
  name: string;
  description: string;
  price: number;
  inStock: boolean;
}

export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  // Get the ID from the URL
  const { id } = await params;

  // Fetch product data using the ID
  const response = await fetch(
    `https://api.example.com/products/${id}`,
    { cache: 'no-store' } // Always fresh data
  );

  if (!response.ok) {
    throw new Error('Failed to fetch product');
  }

  const product: Product = await response.json();

  // Render the product
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>

      <div>
        <strong>Price:</strong> ${product.price}
      </div>

      <div>
        <strong>Availability:</strong>{' '}
        {product.inStock ? 'In Stock' : 'Out of Stock'}
      </div>
    </div>
  );
}
```

## Quick Checklist

Before shipping a pathname-driven detail page, confirm:

- [ ] The route folder uses brackets (e.g., `app/[id]/page.tsx`)
- [ ] The component stays server-side (no `'use client'` needed)
- [ ] The `params` prop is typed as `Promise<{ id: string }>` for Next.js 15+
- [ ] You await the params and read the identifier safely
- [ ] Data fetching logic uses that identifier
- [ ] Rendering handles loading/error states appropriately
- [ ] Types are explicit—never fall back to `any`

## When to Use the Comprehensive Skill Instead

This micro-skill covers the simple "pathname ID fetch" pattern. Use the comprehensive `nextjs-dynamic-routes-params` skill for:
- Catch-all routes (`[...slug]`)
- Optional catch-all routes (`[[...slug]]`)
- Complex multi-parameter routing
- Advanced routing architectures
- Detailed routing decisions

For the simple case of "fetch data by ID from URL", this skill is all you need.

Overview

This skill documents a focused Next.js pattern for fetching data based on URL parameters. It explains creating dynamic route folders like [id] or [slug], accessing route params in server components, and using that identifier to fetch and render item details. The guidance targets common detail pages such as products, posts, and profiles.

How this skill works

Create a dynamic route folder (for example app/[id]/page.tsx), implement a server component, await the params object (Next.js 15+), extract the identifier, and call your API using that value. Return rendered JSX with the fetched data and handle missing or error responses appropriately. Keep the component server-side—do not add 'use client'.

When to use it

  • Building product, blog post, or user profile detail pages that rely on a URL identifier
  • Admin or dashboard pages that drill into a single resource via an ID in the path
  • Simple routing cases where a single dynamic segment (e.g., [id] or [slug]) provides the required context
  • When you need server-side data fetching tied directly to the current pathname
  • When you want a minimal, predictable routing structure without catch-all complexity

Best practices

  • Name dynamic folders with brackets: app/[id]/page.tsx (no brackets = static route)
  • Keep the page a server component and await params for Next.js 15+: const { id } = await params
  • Type params precisely (e.g., Promise<{ id: string }>) and avoid using any
  • Use fetch with cache control (e.g., { cache: 'no-store' }) when up-to-date data is required
  • Handle fetch failures and render clear loading/error states in the UI

Example use cases

  • Product detail page at /products/123 implemented as app/[id]/page.tsx fetching /api/products/123
  • Blog post page at /blog/my-slug implemented as app/[slug]/page.tsx fetching the post by slug
  • Admin order view at /admin/orders/ord-456 implemented as app/[orderId]/page.tsx loading order details
  • Simple docs lookup where a doc id in the path maps to a single API call and render
  • User profile page at /users/alice implemented using a dynamic [username] folder and server fetch

FAQ

Do I need 'use client' in the page component?

No. Keep this as a server component. Adding 'use client' removes server-side access to params and can break the pattern.

How should I type params for Next.js 15+?

Type params as a Promise of an object, e.g., { params }: { params: Promise<{ id: string }> } and await params before reading the id.