← Blog8 min read#next.js#api routes#route handlers

Next.js API Routes vs Route Handlers: When to Use Which in 2026

API Routes are dead in the App Router - but when should you actually switch to Route Handlers? A practical breakdown for Next.js developers in 2026.

Developer writing Next.js API route handler code on laptop screen

The Split That Caught Everyone Off Guard

When Next.js 13 landed the App Router in 2022 and made it stable in 13.4, it quietly deprecated API Routes. Not removed - deprecated. Your /pages/api/* files still work in 2026. But if you're starting a new project on the App Router and you're still writing pages/api/hello.ts, you're fighting the framework instead of working with it.

The confusion is real. Plenty of teams have monorepos where half the codebase uses the Pages Router with pages/api/ and the other half uses the App Router with app/api/. That mixing is actually fine - Next.js supports both in the same project. But you need to know the rules for each, or you'll hit edge cases that are genuinely annoying to debug.

Honestly, the naming doesn't help. "API Routes" is what everyone called the feature in the Pages Router. "Route Handlers" is what Vercel calls the equivalent in the App Router. They do the same conceptual job - handle HTTP requests server-side - but the implementation, the mental model, and the performance characteristics differ enough that picking the wrong one at the wrong time has real consequences.

This guide covers both. You'll walk away knowing exactly which to reach for, and why.

API Routes (Pages Router): What They Are and How They Work

API Routes live in pages/api/. Any file you drop there becomes an endpoint. The export signature looks like this: ``ts // pages/api/users.ts import type { NextApiRequest, NextApiResponse } from 'next' export default function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method === 'GET') { res.status(200).json({ users: [] }) } else { res.status(405).end() } } ` Simple. One default export. You get Node.js-style req and res objects - if you've written Express before, it feels immediately familiar. Method switching is manual; you check req.method` yourself.

That familiarity is the main selling point. API Routes have been around since Next.js 9 (released 2019), and there's an enormous amount of community knowledge, Stack Overflow answers, and tutorials built around them. If you're joining a team that's on a 2021-era Next.js codebase, this is what you're working with.

Worth noting: API Routes always run in the Node.js runtime. You can use fs, native Node modules, database drivers that depend on Node internals - no restrictions. The trade-off is that you can't deploy them to edge runtimes. Every request cold-starts the Node environment, which adds latency on serverless platforms like Vercel unless the function is already warm.

One more thing - API Routes don't have access to middleware in the same way the App Router does. You can add per-route middleware logic, but it's DIY. You're writing your own auth checks, CORS headers, and rate-limiting logic in every handler or wrapping them in higher-order functions. It works. It's just manual.

Route Handlers (App Router): The Modern Approach

Route Handlers live inside app/ and use a route.ts file (not page.tsx). The export signature is completely different - you export named functions named after HTTP methods: ``ts // app/api/users/route.ts import { NextRequest, NextResponse } from 'next/server' export async function GET(request: NextRequest) { return NextResponse.json({ users: [] }, { status: 200 }) } export async function POST(request: NextRequest) { const body = await request.json() return NextResponse.json({ created: true }, { status: 201 }) } ` No switch on req.method`. No default export. Each HTTP verb is its own function, which means TypeScript can actually help you here - unused exports just don't get called.

The big shift is that Route Handlers are built on the Web Fetch API, not Node's http module. Request and Response are the same interfaces you use in browser fetch() calls and in Web Workers. This is intentional: it means Route Handlers can run on the Edge Runtime, with sub-10ms cold starts on Vercel's edge network compared to 200–400ms for a cold Node.js function.

In practice, this matters most for auth middleware, geolocation-aware redirects, and any endpoint that needs to respond fast globally. If you're building a SaaS product that serves users in Tokyo and SΓ£o Paulo, that latency gap between a cold Node function and an edge handler is the difference between a snappy app and one that feels laggy on the first request after a quiet night.

Route Handlers also integrate cleanly with Next.js caching. You can use fetch() with cache: 'force-cache' or next: { revalidate: 60 } inside them, and Next.js will handle caching at the framework level. That's something API Routes never had - you were on your own to implement caching headers manually. That said, the caching model in Next.js 14+ is complex enough that it deserves its own article, which is exactly what Next.js caching strategies covers.

Quick aside: if you want to opt a specific Route Handler into the Node.js runtime instead of the edge, you just add one line at the top of the file: ``ts export const runtime = 'nodejs' `` You get the full Node.js environment without losing the named-export syntax. Best of both worlds.

Side-by-Side: Key Differences That Actually Matter

Let's cut to what changes day-to-day. The table below isn't exhaustive, but it covers the decisions you'll hit most often. `` Feature | API Routes (Pages) | Route Handlers (App) -----------------------|----------------------|---------------------- Runtime default | Node.js | Node.js (edge opt-in) HTTP method routing | Manual (req.method) | Named exports Request object | NextApiRequest | NextRequest (Web API) Response object | NextApiResponse | NextResponse (Web API) Middleware support | DIY | next.config + middleware.ts Streaming responses | Limited | Native (ReadableStream) Caching integration | None | Built-in (fetch cache) Edge deployable | No | Yes (opt-in) ``

Streaming is the one feature that Route Handlers unlock that has no real equivalent in API Routes. You can return a ReadableStream directly from a Route Handler, which is how you'd build a streaming AI response, a chunked CSV export, or a server-sent events endpoint: ``ts // app/api/stream/route.ts export async function GET() { const stream = new ReadableStream({ start(controller) { controller.enqueue('data: hello\n\n') controller.enqueue('data: world\n\n') controller.close() } }) return new Response(stream, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', } }) } `` Trying to do this cleanly with API Routes requires hacks. Route Handlers make it native.

Look, there's one footgun worth calling out. In the App Router, a route.ts and a page.tsx cannot coexist in the same directory. If you have app/dashboard/page.tsx and you try to add app/dashboard/route.ts, Next.js will throw a build error. Move the route handler to a subdirectory like app/dashboard/api/route.ts or use a dedicated app/api/ prefix.

One more thing - dynamic segments work differently too. In API Routes you'd use pages/api/users/[id].ts and read req.query.id. In Route Handlers, it's app/api/users/[id]/route.ts and the second argument to each method function carries the params: ``ts // app/api/users/[id]/route.ts export async function GET( request: NextRequest, { params }: { params: { id: string } } ) { return NextResponse.json({ userId: params.id }) } ``

When to Use API Routes in 2026

The honest answer is: mostly when you're working with existing Pages Router code. Starting a new project on the App Router and reaching for pages/api/ is backwards. You're adding a second routing system to your project for no benefit.

That said, there are legitimate 2026 scenarios where API Routes make sense. If your team has 50,000 lines of Pages Router code and you're incrementally migrating to the App Router, you're not going to rewrite every endpoint on day one. API Routes keep working, keep deploying, and keep paying the bills while the migration happens file-by-file over months. That's not technical debt - that's pragmatism.

API Routes also shine when you need tight integration with Node.js-specific packages that don't work at the edge. Think: heavy PDF generation with Puppeteer, certain native addons, or libraries that use __dirname or Node's crypto module in ways that break in edge environments. Yes, you can set export const runtime = 'nodejs' on a Route Handler, but if the entire codebase is already Pages Router and Node-heavy, staying with API Routes is the path of least resistance.

The pattern that trips people up: you're building a Next.js app for a UI library or a design tool - say, something like an Empire UI template starter - and you want a quick internal API for saving user preferences. If you're already on the App Router for your pages, add a Route Handler. Don't mix in pages/api/ just because you've used it before.

When to Use Route Handlers in 2026

New App Router project? Route Handlers, full stop. There's no reason to use anything else. The DX is better, the typing is cleaner, and you get edge deployment and streaming out of the box.

Route Handlers are particularly good for three categories of endpoints. First: auth callbacks. OAuth providers like GitHub or Google redirect back to your app with a code parameter. Handling that exchange at the edge means the redirect resolves in under 50ms globally instead of waiting for a Node.js cold start. Second: webhooks from Stripe, GitHub, or any third-party service that needs a fast 200 response before it retries. Edge latency wins here. Third: streaming endpoints - AI chat completions, live logs, progress feeds. The Web Streams API integration is clean and production-ready.

If you're building any kind of UI that shows real-time feedback - a glassmorphism dashboard with live stats, an animated status feed - a streaming Route Handler is what connects the dots between your server data and the client without WebSockets overhead. Worth knowing for that use case.

The one time Route Handlers feel awkward is when you need full Node.js compatibility AND you want the named-export syntax. You can have it - export const runtime = 'nodejs' handles this - but edge deployment disappears as a benefit. In that case, the Route Handler wins over the API Route on DX alone: named exports per method, cleaner TypeScript, framework-level caching hooks.

Quick aside: if you're working with Next.js Server Actions too, remember that Server Actions are for mutations triggered from client components - form submissions, button clicks. Route Handlers are for external HTTP consumers: mobile apps, third-party services, or your own client-side fetch() calls. They're not competitors. They fill different roles.

Migration Path: Moving API Routes to Route Handlers

Migrating is straightforward for most endpoints. The main work is translating the request/response API from Node-style to Web API style: ``ts // Before - pages/api/products.ts export default function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== 'GET') return res.status(405).end() const { category } = req.query res.status(200).json({ products: [], category }) } // After - app/api/products/route.ts export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url) const category = searchParams.get('category') return NextResponse.json({ products: [], category }) } ` Query parameters move from req.query (an object Next.js parsed for you) to new URL(request.url).searchParams (standard Web API). Body parsing moves from req.body (pre-parsed by Next.js) to await request.json() or await request.text()` - you call it explicitly.

Cookies and headers are the other place where the API changes. In API Routes you'd do res.setHeader('Set-Cookie', ...) or use req.headers. In Route Handlers: ``ts import { cookies, headers } from 'next/headers' export async function GET() { const cookieStore = cookies() const token = cookieStore.get('token') return NextResponse.json({ authed: !!token }) } ` The next/headers` module is specific to the App Router. It's a clean API, but it's a new import to learn.

Honestly, the migration of a typical CRUD endpoint takes about 15 minutes once you know the pattern. The tricky ones are endpoints that do file streaming, multipart form parsing, or response piping. Test those manually after migrating - they're the ones most likely to have subtle behavioral differences at the edge vs. Node runtime.

If you want to see how these backend patterns pair with polished frontend UI work, check out the page transitions guide - it's a good complement to understanding the full Next.js request lifecycle from route to render. And for keeping your components looking sharp while the backend evolves, browsing through the Empire UI component library gives you production-ready pieces that don't need babysitting.

FAQ

Can I use both API Routes and Route Handlers in the same Next.js project?

Yes. Next.js supports running the Pages Router and App Router side-by-side. pages/api/ endpoints and app/api/ Route Handlers both work simultaneously - useful during incremental migrations.

Do Route Handlers replace Server Actions?

No, they serve different purposes. Route Handlers handle external HTTP requests from any client. Server Actions are called directly from React components for mutations - they're not exposed as public HTTP endpoints.

Are API Routes deprecated?

Not removed, but they're a Pages Router feature with no planned updates. They'll keep working, but new Next.js features (edge caching, streaming, Web API integration) are Route Handler-only.

Which is faster: API Routes or Route Handlers at the edge?

Route Handlers at the edge runtime are significantly faster on cold starts - often under 10ms vs. 200–400ms for a cold Node.js API Route function. Warm functions are comparable.

Free components in 41 styles
React & Tailwind, copy-paste ready.
Browse β†’

Read next

React Architecture & Patterns: The Complete 2026 Guide β†’Next.js App Router vs Pages Router in 2026: Which Should You Use? β†’Next.js vs Remix in 2026: Which One Should You Use? β†’Vite + React vs Next.js in 2026: Which Scaffold to Choose β†’