Next-js App-Router dynamic route: ‘params should be awaited’ warning in /api/…/[id]/… handler — why and how to fix?

Next-js App-Router dynamic route: ‘params should be awaited’ warning in /api/…/[id]/… handler — why and how to fix?

I added a dynamic route handler:

src/app/api/meetings/[meetingId]/respond/route.ts
    import { NextRequest, NextResponse } from 'next/server';
    import { getServerSession } from 'next-auth';
    import { authOptions } from '@/src/app/api/auth/[...nextauth]/auth-options';
    import { getMeetingsCollection } from '@/src/lib/db';
    import { ObjectId } from 'mongodb';
    
    interface RequestBody {
      action: 'ACCEPT' | 'DECLINE';
    }
    
    export async function PUT(
      req: NextRequest,
      { params }: { params: { meetingId: string } }   // <-- read params like usual
    ) {
      const session = await getServerSession(authOptions);
      const { meetingId } = params;                   // <-- triggers warning
      /* ...update meeting... */
    }

Every time I hit

PUT /api/meetings/6828463d63c61a6feda6ab12/respond

the dev-server logs:

Error: Route "/api/meetings/[meetingId]/respond" used `params.meetingId`.
`params` should be awaited before using its properties.
Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis

I’ve always used the App Router; nothing was upgraded. I just created this [meetingId] folder.

What is this warning and how do I fix it?

Answer

In the App Router, when a route (page, layout, or route handler) lives inside a dynamic segment ([meetingId], [slug], etc.) the params object is a Promise.

Accessing it synchronously causes the “params should be awaited” warning.

Fix = await the promise (or use React.use() in a client component).

// src/app/api/meetings/[meetingId]/respond/route.ts
import { NextRequest, NextResponse } from 'next/server';
export const dynamic = 'force-dynamic';  // optional: disables static optimisation

import { getServerSession } from 'next-auth';
import { authOptions } from '@/src/app/api/auth/[...nextauth]/auth-options';
import { getMeetingsCollection } from '@/src/lib/db';
import { ObjectId } from 'mongodb';
import { MeetingSchema } from '@/src/schemas/MeetingSchema';

/* ---- types ---- */
interface RequestBody       { action: 'ACCEPT' | 'DECLINE'; }
interface RouteContext      { params: Promise<{ meetingId: string }>; }

export async function PUT(req: NextRequest, context: RouteContext) {
  /* 1. auth check ------------------------------------------------------- */
  const session = await getServerSession(authOptions);
  if (!session?.user?.id) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  /* 2. await the params  ---------------------------------------------- */
  const { meetingId } = await context.params;

  if (!ObjectId.isValid(meetingId)) {
    return NextResponse.json({ error: 'Invalid meetingId' }, { status: 400 });
  }

  /* 3. handle ACCEPT / DECLINE ----------------------------------------- */
  const meetings  = await getMeetingsCollection();
  const _id       = new ObjectId(meetingId);
  const body      = (await req.json()) as RequestBody;
  const now       = new Date();

  if (body.action === 'ACCEPT') {
    const updated = await meetings.findOneAndUpdate(
      { _id, inviteeId: new ObjectId(session.user.id), status: 'PENDING_INVITEE_ACTION' },
      { $set: { status: 'ACCEPTED', selectedTime: '$proposedTime', updatedAt: now } },
      { returnDocument: 'after' }
    );
    return NextResponse.json({ meeting: updated }, { status: 200 });
  }

  if (body.action === 'DECLINE') {
    const updated = await meetings.findOneAndUpdate(
      { _id, inviteeId: new ObjectId(session.user.id), status: 'PENDING_INVITEE_ACTION' },
      { $set: { status: 'DECLINED_BY_INVITEE', updatedAt: now } },
      { returnDocument: 'after' }
    );
    return NextResponse.json({ meeting: updated }, { status: 200 });
  }

  return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
}
  • await context.params unwraps the Promise so you can use meetingId.

  • Adding export const dynamic = 'force-dynamic' is optional; it just tells Next.js not to attempt static optimisation for this handler, eliminating a separate warning.

Restart the dev server. no more “params should be awaited” message, and the handler works normally.

Enjoyed this question?

Check out more content on our blog or follow us on social media.

Browse more questions