Route Handlers
Use route.ts when HTTP is the boundary you actually need: public APIs, webhooks, callbacks, feeds, files, and external integrations.
Create an HTTP boundary for an HTTP consumer
Route Handlers support standard HTTP methods and Web Request/Response APIs. They are a natural fit when a browser, mobile app, webhook provider, or another service needs an HTTP endpoint.
They are not a compulsory middle layer between a Server Component and your own database.
Do not fetch your own API just because it exists
If a Server Component and a Route Handler eventually need the same query, put that query in a shared server-side function. Let both entry points call it.
Sending the Server Component through HTTP first adds serialization, networking, error handling, and another boundary without necessarily adding value.
// lib/projects.ts
export async function listProjects() {
return db.query.projects.findMany()
}
// app/projects/page.tsx
const projects = await listProjects()
// app/api/projects/route.ts
export async function GET() {
return Response.json(await listProjects())
}Pick the boundary based on the caller
- Server Component needing internal data → call the server data/service layer directly.
- Form mutation → often a Server Action.
- External webhook → Route Handler.
- OAuth callback → Route Handler.
- Public JSON API → Route Handler.
- Generated feed or file response → Route Handler.
Can you explain “Route Handlers” without reaching for the API reference?
Describe the main trade-off, one failure mode, and the decision you would make in a real project.