Route Handlers in Depth: Cookies, Headers, CORS and Webhooks

Intermediate
12 min

Route Handlers in Depth: Cookies, Headers, CORS and Webhooks

The basics of route.ts cover JSON in and JSON out. Production endpoints also set cookies, answer preflight requests from other origins, verify webhook signatures and finish work after the response is sent. After this lesson you will be able to handle all of these with NextRequest, NextResponse and the helpers in next/server.

The Request and Response Toolkit

| Need | API | |---|---| | Query string | request.nextUrl.searchParams.get("q") | | JSON, text or form body | await request.json(), .text(), .formData() | | Read cookies | request.cookies.get("token")?.value or (await cookies()).get("token") | | Read headers | request.headers.get("authorization") or await headers() | | JSON response | NextResponse.json(data, { status: 201 }) | | Dynamic params | Second argument: { params }: { params: Promise<{ id: string }> } |

Both classes extend the Web Request and Response, so Fetch API code works unchanged.

Cookies and Headers

Reading a cookie and setting one on the way out:

typescript
// app/api/session/route.ts import { NextResponse, type NextRequest } from "next/server"; export async function POST(request: NextRequest) { const { userId } = await request.json(); const token = await createSessionToken(userId); const response = NextResponse.json({ ok: true }); response.cookies.set("session", token, { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax", path: "/", maxAge: 60 * 60 * 24 * 7, }); return response; }

To log out, read request.cookies.get("session")?.value, revoke it, and call response.cookies.delete("session"). The cookies() function from next/headers also works in handlers. Reading cookies or headers makes a GET handler dynamic.

CORS for Cross-Origin Clients

A mobile app or a different domain calling your API needs CORS headers, including an OPTIONS handler for the browser's preflight request:

typescript
// app/api/public/quotes/route.ts import { NextResponse } from "next/server"; const cors = { "Access-Control-Allow-Origin": "https://app.example.com", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization", }; export async function OPTIONS() { return new NextResponse(null, { status: 204, headers: cors }); } export async function GET() { const quotes = await getQuotes(); return NextResponse.json(quotes, { headers: cors }); }

For headers shared by many routes, the headers() option in next.config.ts or the proxy file (next lesson) is less repetitive.

Verifying Webhooks

Payment providers and Git hosts POST signed events to your server. The sample at the top of this lesson shows the three rules: read the raw body with request.text() because the signature covers the exact bytes, compute your own HMAC with the shared secret, and compare with timingSafeEqual to avoid timing attacks. Only then parse the JSON. Respond quickly with a 2xx; providers retry on failures.

Finishing Work After the Response

Logging, analytics and notifications should not delay the reply. The after function schedules work to run once the response has been sent:

typescript
import { after, NextResponse } from "next/server"; export async function POST(request: Request) { const order = await createOrder(await request.json()); after(async () => { await sendConfirmationEmail(order); // runs after the client has its response }); return NextResponse.json(order, { status: 201 }); }

Handlers can also stream by passing a ReadableStream to new Response(), which is how chat endpoints deliver tokens incrementally.

Common mistakes

  • Parsing a webhook body with request.json() before verifying the signature; re-serialised JSON will not match.
  • Forgetting the OPTIONS handler, so browsers fail the preflight even though GET sends the right headers.
Quick Quiz
Question 1 of 3

Why must a webhook handler read the body with `request.text()` instead of `request.json()`?

Key Takeaways

  • NextRequest and NextResponse extend the Web Fetch API with cookies, nextUrl and JSON helpers.
  • Set cookies on the response object with httpOnly, secure and sameSite options.
  • Cross-origin clients need CORS headers on every response plus an OPTIONS preflight handler.
  • Verify webhooks against the raw body with an HMAC and timingSafeEqual before parsing.
  • Use after() for work that should not delay the response.

Next lesson: Proxy (Middleware): Redirects, Rewrites and Request Interception — run code before a request reaches any route.

Route Handlers in Depth: Cookies, Headers, CORS and Webhooks - Next.js | CodeYourCraft | CodeYourCraft