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?
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';
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.