Complete Guide: Accessing Params in Next.js 15 with TypeScript

9 mins read
0 Like
10 Views
Next.js 15 introduced a significant change in how we access route parameters and search params. The most important change is that params must now be awaited before accessing their values. This guide will walk you through everything you need to know about handling params in Next.js 15 with TypeScript.

What Changed in Next.js 15?

In previous versions of Next.js, params were synchronously available:

TypeScript
1// ❌ Old way (Next.js 14 and below)
2export default function Page({ params }: { params: { id: string } }) {
3  const id = params.id; // Direct access
4  return <div>Post ID: {id}</div>;
5}

In Next.js 15, params are now asynchronous and must be awaited:

TypeScript
1// ✅ New way (Next.js 15+)
2export default async function Page({ params }: { params: Promise&lt;{ id: string }&gt; }) {
3  const { id } = await params; // Must await
4  return <div>Post ID: {id}</div>;
5}

Understanding Route Segments

Before diving into examples, let's understand the different types of route segments:

  • Static segments: /about, /contact
  • Dynamic segments: /posts/[id], /users/[userId]
  • Catch-all segments: /blog/[...slug]
  • Optional catch-all segments: /shop/[[...categories]]

1. Accessing Dynamic Route Params [id]/page.tsx

File Structure

Plain Text
1app/
2  posts/
3    [id]/
4      page.tsx

Server Component Implementation

TypeScript
1// app/posts/[id]/page.tsx
2interface PageProps {
3  params: Promise&lt;{
4    id: string;
5  }&gt;;
6}
7
8export default async function PostPage({ params }: PageProps) {
9  // ✅ Await the params
10  const { id } = await params;
11  
12  // Now you can use the id
13  console.log('Post ID:', id);
14  
15  return (
16    <div>
17      <h1>Post Details</h1>
18      <p>You are viewing post with ID: {id}</p>
19    </div>
20  );
21}
22
23// Optional: Generate metadata using params
24export async function generateMetadata({ params }: PageProps) {
25  const { id } = await params;
26  
27  return {
28    title: `Post ${id}`,
29    description: `Details for post ${id}`,
30  };
31}

URLs that match this route:

  • /posts/123id = "123"
  • /posts/hello-worldid = "hello-world"
  • /posts/my-first-postid = "my-first-post"

2. Accessing Catch-All Route Params [...id]/page.tsx

File Structure

Plain Text
1app/
2  blog/
3    [...slug]/
4      page.tsx

Server Component Implementation

TypeScript
1// app/blog/[...slug]/page.tsx
2interface PageProps {
3  params: Promise&lt;{
4    slug: string[];
5  }&gt;;
6}
7
8export default async function BlogPage({ params }: PageProps) {
9  // ✅ Await the params
10  const { slug } = await params;
11  
12  // slug is an array of path segments
13  console.log('Slug array:', slug);
14  
15  // Create breadcrumb navigation
16  const breadcrumbs = slug.map((segment, index) =&gt; ({
17    name: segment,
18    path: `/blog/${slug.slice(0, index + 1).join('/')}`,
19  }));
20  
21  return (
22    <div>
23      <h1>Blog Post</h1>
24      <nav>
25        {breadcrumbs.map((crumb, index) =&gt; (
26          <span>
27            <a href="{crumb.path}">{crumb.name}</a>
28            {index &lt; breadcrumbs.length - 1 &amp;&amp; ' / '}
29          </span>
30        ))}
31      </nav>
32      <p>Full path: {slug.join('/')}</p>
33    </div>
34  );
35}

URLs that match this route:

  • /blog/javascriptslug = ["javascript"]
  • /blog/web-development/reactslug = ["web-development", "react"]
  • /blog/2024/january/new-yearslug = ["2024", "january", "new-year"]

3. Multiple Dynamic Segments

You can combine multiple dynamic segments:

File Structure

Plain Text
1app/
2  users/
3    [userId]/
4      posts/
5        [postId]/
6          page.tsx

Implementation

TypeScript
1// app/users/[userId]/posts/[postId]/page.tsx
2interface PageProps {
3  params: Promise&lt;{
4    userId: string;
5    postId: string;
6  }&gt;;
7}
8
9export default async function UserPostPage({ params }: PageProps) {
10  const { userId, postId } = await params;
11  
12  return (
13    <div>
14      <h1>User Post</h1>
15      <p>User ID: {userId}</p>
16      <p>Post ID: {postId}</p>
17    </div>
18  );
19}

Matching URLs:

  • /users/john/posts/my-storyuserId = "john", postId = "my-story"

4. Accessing Search Params (Query Parameters)

Search params (query strings) are handled differently from route params.

Server Side - Search Params

TypeScript
1// app/search/page.tsx
2interface PageProps {
3  searchParams: Promise&lt;{
4    q?: string;
5    category?: string;
6    page?: string;
7  }&gt;;
8}
9
10export default async function SearchPage({ searchParams }: PageProps) {
11  // ✅ Await the searchParams (Next.js 15+)
12  const { q, category, page } = await searchParams;
13  
14  // Convert page to number with fallback
15  const currentPage = page ? parseInt(page, 10) : 1;
16  
17  return (
18    <div>
19      <h1>Search Results</h1>
20      {q &amp;&amp; <p>Searching for: "{q}"</p>}
21      {category &amp;&amp; <p>Category: {category}</p>}
22      <p>Page: {currentPage}</p>
23    </div>
24  );
25}

URL Examples:

  • /search?q=nextjsq = "nextjs"
  • /search?q=react&amp;category=tutorial&amp;page=2q = "react", category = "tutorial", page = "2"

5. Client Side - Using useParams and useSearchParams

For client components, Next.js provides hooks to access params and search params.

useParams Hook

TypeScript
1'use client';
2
3import { useParams } from 'next/navigation';
4
5export default function ClientComponent() {
6  // ✅ useParams returns the current params
7  const params = useParams&lt;{ id: string }&gt;();
8  
9  // No need to await on client side
10  const id = params.id;
11  
12  return (
13    <div>
14      <p>Current ID from client: {id}</p>
15    </div>
16  );
17}

useSearchParams Hook

TypeScript
1'use client';
2
3import { useSearchParams } from 'next/navigation';
4import { Suspense } from 'react';
5
6function SearchComponent() {
7  const searchParams = useSearchParams();
8  
9  const query = searchParams.get('q');
10  const category = searchParams.get('category');
11  
12  return (
13    <div>
14      <p>Query: {query}</p>
15      <p>Category: {category}</p>
16    </div>
17  );
18}
19
20// ✅ Wrap components using useSearchParams with Suspense
21export default function ClientSearchPage() {
22  return (
23    Loading...}&gt;
24      
25    
26  );
27}

Updating Search Params on Client Side

TypeScript
1'use client';
2
3import { useRouter, useSearchParams } from 'next/navigation';
4import { useCallback } from 'react';
5
6export default function FilterComponent() {
7  const router = useRouter();
8  const searchParams = useSearchParams();
9  
10  const updateFilter = useCallback((key: string, value: string) =&gt; {
11    const params = new URLSearchParams(searchParams.toString());
12    params.set(key, value);
13    
14    // Update the URL
15    router.push(`?${params.toString()}`);
16  }, [router, searchParams]);
17  
18  return (
19    <div>
20      <button> updateFilter('category', 'tech')}&gt;
21        Filter by Tech
22      </button>
23      <button> updateFilter('sort', 'date')}&gt;
24        Sort by Date
25      </button>
26    </div>
27  );
28}

6. TypeScript Best Practices

Define Proper Types

TypeScript
1// types/page-props.ts
2export interface DynamicPageProps {
3  params: Promise;
4  searchParams: Promise&lt;{ [key: string]: string | string[] | undefined }&gt;;
5}
6
7// Usage
8interface PostPageProps extends DynamicPageProps&lt;{ id: string }&gt; {}
9
10export default async function PostPage({ params, searchParams }: PostPageProps) {
11  const { id } = await params;
12  const search = await searchParams;
13  
14  // Fully typed!
15  return <div>Post {id}</div>;
16}

Handle Optional Parameters

TypeScript
1interface OptionalParamsPageProps {
2  params: Promise&lt;{
3    id?: string;
4    category?: string;
5  }&gt;;
6}
7
8export default async function OptionalPage({ params }: OptionalParamsPageProps) {
9  const { id, category } = await params;
10  
11  // Handle undefined values
12  if (!id) {
13    return <div>No ID provided</div>;
14  }
15  
16  return (
17    <div>
18      <p>ID: {id}</p>
19      {category &amp;&amp; <p>Category: {category}</p>}
20    </div>
21  );
22}

7. Error Handling and Validation

Validate Params

TypeScript
1import { notFound } from 'next/navigation';
2
3interface PageProps {
4  params: Promise&lt;{ id: string }&gt;;
5}
6
7export default async function PostPage({ params }: PageProps) {
8  const { id } = await params;
9  
10  // Validate the ID format
11  if (!/^\d+$/.test(id)) {
12    notFound(); // Triggers 404 page
13  }
14  
15  const postId = parseInt(id, 10);
16  
17  return <div>Valid Post ID: {postId}</div>;
18}

Handle Async Errors

TypeScript
1export default async function PostPage({ params }: PageProps) {
2  try {
3    const { id } = await params;
4    // Your component logic
5    return <div>Post {id}</div>;
6  } catch (error) {
7    console.error('Error accessing params:', error);
8    return <div>Error loading page</div>;
9  }
10}

8. Common Patterns and Examples

Loading States with Suspense

TypeScript
1import { Suspense } from 'react';
2
3export default async function PostLayout({
4  children,
5  params,
6}: {
7  children: React.ReactNode;
8  params: Promise&lt;{ id: string }&gt;;
9}) {
10  const { id } = await params;
11  
12  return (
13    <div>
14      <header>Post {id}</header>
15      Loading content...</div>}&gt;
16        {children}
17      
18    
19  );
20}

Combining Route and Search Params

TypeScript
1interface CombinedPageProps {
2  params: Promise&lt;{ category: string }&gt;;
3  searchParams: Promise&lt;{
4    page?: string;
5    limit?: string;
6    sort?: string;
7  }&gt;;
8}
9
10export default async function CategoryPage({ params, searchParams }: CombinedPageProps) {
11  // Await both params and searchParams
12  const { category } = await params;
13  const { page = '1', limit = '10', sort = 'date' } = await searchParams;
14  
15  const pageNumber = parseInt(page, 10);
16  const limitNumber = parseInt(limit, 10);
17  
18  return (
19    <div>
20      <h1>Category: {category}</h1>
21      <p>Page: {pageNumber}</p>
22      <p>Limit: {limitNumber}</p>
23      <p>Sort by: {sort}</p>
24    </div>
25  );
26}

Summary

Key takeaways for Next.js 15 params handling:

  1. Always await params in server components: const { id } = await params
  2. searchParams are also async and need to be awaited: await searchParams
  3. Client-side hooks (useParams, useSearchParams) don't need awaiting
  4. Wrap useSearchParams components with Suspense
  5. Type your params properly with TypeScript interfaces
  6. Handle errors and validate param values
  7. Use notFound() for invalid routes

This new async pattern in Next.js 15 provides better performance and more predictable behavior, especially in edge environments. While it requires updating existing code, the benefits include improved type safety and better error handling.

Share:

Comments

0
Join the conversation

Sign in to share your thoughts and connect with other readers

No comments yet

Be the first to share your thoughts!