Hirefullstack – Software Engineering & IT-Beratung aus Berlin
← Back to overview
Core 10 min read

Route handlers

When something from outside needs access

In one sentence

A route.ts answers an address with data instead of a page. Meant for access from outside – not for your own interface.

Reading is the server component's job, writing the server action's. That leaves a gap: what if a third-party system wants to talk to you? That is what the Route handlerA `route.ts` that answers an address without being a page – your way to a custom API. is for.

app/api/posts/route.ts
export async function GET() {
  const posts = await db.post.findMany();
  return Response.json(posts);
}

export async function POST(request: Request) {
  const data = await request.json();
  const created = await db.post.create({ data });
  return Response.json(created, { status: 201 });
}
The file is called route.ts, the functions are named after the HTTP methods. You return an ordinary Response.
Pitfall page.tsx and route.ts do not mix

Only one of the two may live in a folder. An address is either a page or an endpoint. That is why handlers usually sit under app/api/….

What they are really for

Reach for it when …
  • +

    Webhooks: payment providers, a CMS, GitHub calling you

  • +

    A public interface for third-party systems or a mobile app

  • +

    Serving files: generated images, PDFs, CSV exports

  • +

    OAuth callbacks and similar redirects

Skip it when …
  • Your own server component calling your own route – a network detour to yourself

  • Forms in your own interface – server actions are shorter and safer

  • Data for your own client component that could just be passed down

Put another way

Server actions are the front door for family – short, direct, no formalities. A route handler is the delivery entrance: documented, with an address, for people from outside.

What is inside

app/api/search/route.ts
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const q = searchParams.get("q") ?? "";

  if (!q) {
    return Response.json({ error: "q is missing" }, { status: 400 });
  }

  const hits = await db.post.findMany({
    where: { title: { contains: q } },
  });

  return Response.json(hits);
}
app/api/posts/[id]/route.ts
export async function DELETE(
  request: Request,
  { params }: { params: Promise<{ id: string }> },
) {
  const { id } = await params;      // await here too
  await db.post.delete({ where: { id } });
  return new Response(null, { status: 204 });
}
Careful Route handlers are completely public

Unlike a page there is no layout and no check in front of them. Whoever knows the address can call it. So every handler checks for itself: is there a valid key? Does the webhook signature match? Is this person allowed?

Caching

cache.ts
// GET handlers are dynamic by default – every request runs through.
// To cache the result:
export const revalidate = 60;

// Or explicitly always fresh:
export const dynamic = "force-dynamic";
In more depth: a webhook that revalidates optional

The most common sensible use: a CMS reports that something was published, and your site clears the matching cache in response.

app/api/revalidate/route.ts
import { revalidateTag } from "next/cache";

export async function POST(request: Request) {
  const key = request.headers.get("x-webhook-key");
  if (key !== process.env.WEBHOOK_KEY) {
    return new Response("Not allowed", { status: 401 });
  }

  const { type } = await request.json();
  revalidateTag(type);                 // e.g. "posts"

  return Response.json({ ok: true });
}
Tip Actually check the key

Without that first check anyone could make your site rebuild as often as they like. The key belongs in an environment variable without NEXT_PUBLIC_ – otherwise it ends up in the browser.

Does it stick?

4 questions on this lesson. Wrong answers show up in your stats.

Hirefullstack

Need React firepower on your team?

We have been building React and Next.js applications for clients across Germany for years – as a single expert, as reinforcement for an existing team, or as a complete Scrum team.

Talk about your project →