nextjs-pathname-id-fetch

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Next.js: Pathname ID Fetch Pattern

Next.js:路径名ID获取模式

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]
).
当页面需要根据URL中的标识符加载数据时,即可使用此模式。常见场景包括:
  • 产品、文章或用户的详情页(
    /products/{id}
    /blog/{slug}
  • 可深入查看选定资源的管理控制台(
    /admin/orders/{orderId}
  • 带有嵌套路径的文档或知识库(
    /docs/getting-started/installation
如果需求要求数据根据当前URL路径变化,那么路由段必须是动态的(例如
[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.
✅ 推荐实现方式
1. 创建动态文件夹:app/[id]/page.tsx
2. 获取参数:const { id } = await params;
3. 使用该标识符获取数据
4. 渲染请求的信息
❌ 常见误区
在此场景下使用app/page.tsx将无法获取每个路径对应的标识符。

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>
  );
}
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' }
app/
└── [id]/                 ← Dynamic route folder with brackets
    └── page.tsx          ← Server component page
URL映射:
  • /123
    → params =
    { id: '123' }
  • /abc
    → params =
    { id: 'abc' }
  • /product-xyz
    → params =
    { id: 'product-xyz' }

Key Rules

核心规则

1. Folder Name MUST Use Brackets

1. 文件夹名称必须使用方括号

✅ 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)
✅ 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)

2. 默认使用Server Component

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 }) { ... }
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+)

3. Next.js 15+中必须等待Params

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
  // ...
}
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

4. 保持简洁 - 不要过度嵌套

✅ 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
).
✅ app/[id]/page.tsx              (simple, clean)
❌ app/products/[id]/page.tsx     (only if explicitly required!)
除非需求明确要求使用
/products/[id]
,否则请将结构保持在顶层(
app/[id]/page.tsx
)。

TypeScript: NEVER Use
any
Type

TypeScript:绝不要使用
any
类型

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) {
    // ...
  }
}
此代码库已启用
@typescript-eslint/no-explicit-any
。使用
any
会导致构建失败。
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;
  // ...
}
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}`);
  // ...
}
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>
  );
}
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
在发布基于路径名的详情页之前,请确认:
  • 路由文件夹使用方括号(例如
    app/[id]/page.tsx
  • 组件保持在服务端(无需添加
    'use client'
  • 对于Next.js 15+,
    params
    属性的类型为
    Promise<{ id: string }>
  • 已等待params并安全读取标识符
  • 数据获取逻辑使用了该标识符
  • 渲染逻辑适当处理了加载/错误状态
  • 类型明确——绝不使用
    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.
此微技能涵盖了简单的“路径名ID获取”模式。在以下场景中,请使用完整的
nextjs-dynamic-routes-params
技能:
  • 捕获所有路由(
    [...slug]
  • 可选捕获所有路由(
    [[...slug]]
  • 复杂的多参数路由
  • 高级路由架构
  • 详细的路由决策
对于“通过URL中的ID获取数据”的简单场景,本技能已足够。