Skip to content

Handling Params as Promises in Next.js 15

Problem

Starting with Next.js 15, accessing route parameters (params) in dynamic routes has changed significantly. The framework now passes params as a Promise instead of a plain object, which causes errors when trying to access parameters directly:

typescript
export default async function Page({ params }: { params: { id: string } }) {
  const id = params.id; // ❌ Error: "A param property was accessed directly"
  return <div>Product ID: {id}</div>;
}

This change is a breaking modification in Next.js 15 that affects dynamic routes like /product/[id]/page.tsx.

Solution

Option 1: Using React.use() in Client Components

For client components ("use client"), use React's use() hook to unwrap the Promise:

typescript
'use client';

import { use } from 'react';

export default function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = use(params); 
  return <div>Product ID: {id}</div>;
}

INFO

The use() hook is a React API specifically designed for consuming Promises and context in React components. It's the recommended approach for client components.

Option 2: Using await in Server Components

For server components (which can be async), use await to unwrap the Promise:

typescript
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params; 
  return <div>Product ID: {id}</div>;
}

Option 3: Passing Params from Server to Client Components

When you need to use the parameters in a client component, extract them in a server component first:

typescript
// Server component: /product/[id]/page.tsx
import ProductPage from "./productPage";

export default async function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  return <ProductPage id={id} />;
}
typescript
// Client component: /product/[id]/productPage.tsx
"use client";

export default function ProductPage({ id }: { id: string }) {
  return <div>Product ID: {id}</div>;
}

Why This Change?

Next.js 15 made params a Promise to support more advanced routing patterns and improve performance through concurrent rendering. This change:

  • Enables better integration with React's concurrent features
  • Allows for more flexible data fetching patterns
  • Prepares for future Next.js features that require asynchronous route resolution

Migration Notice

While Next.js 15 still supports direct access to param properties for backward compatibility, this support will be removed in a future version. It's recommended to update your code now.

Best Practices

  1. Use use() in client components - This is the most straightforward approach
  2. Use await in server components - Keep server components async when needed
  3. Pass data explicitly - When using client components, extract params in server components and pass them as props
  4. Update existing code - Migrate all direct params.property access to the new patterns

Example: Complete Implementation

typescript
// Client component example with multiple params
'use client';

import { use } from 'react';

interface PageParams {
  id: string;
  category: string;
}

export default function ProductPage({ params }: { params: Promise<PageParams> }) {
  const { id, category } = use(params);
  
  return (
    <div>
      <h1>Product Details</h1>
      <p>ID: {id}</p>
      <p>Category: {category}</p>
    </div>
  );
}

This change in Next.js 15 represents the framework's evolution toward more React-aligned patterns and prepares developers for future advancements in the React ecosystem.