Route Handlers

Master Route Handlers in Next.js — the App Router replacement for API routes, supporting all HTTP methods, streaming responses, and edge runtime.

Last reviewed: January 2025

·Next.js 13.4+Next.js 14Next.js 15
✅
Quick Understanding: Route Handlers are files named route.ts inside the app/ directory. They handle HTTP requests (GET, POST, PUT, DELETE, PATCH) and replace the pages/api/ API routes from the Pages Router. They have access to the full Web Request/Response API.

🔍 What Is It?

Route Handlers are server-side request handlers defined in route.ts files inside app/:

  • →File: app/api/products/route.ts
  • →URL: /api/products
  • →Exports: Named functions matching HTTP methods: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
  • →API: Web standard Request and Response objects (not Node.js req/res)

Key differences from Pages Router API routes:

  • →Use NextRequest / NextResponse (extended Web API)
  • →Support streaming responses
  • →Can run on Edge Runtime
  • →Support all HTTP methods with named exports
  • →Can be co-located with the route they serve

🎯 Why Do We Need It?

Route Handlers are needed for:

  • →External API consumers — mobile apps, third-party integrations
  • →Webhooks — receiving events from Stripe, GitHub, CMS
  • →Form submissions — when not using Server Actions
  • →File uploads — handling multipart form data
  • →OAuth callbacks — handling redirect-based auth flows
  • →Streaming responses — Server-Sent Events, streaming AI responses

For internal data fetching from Server Components, you do NOT need Route Handlers — fetch directly from the DB.

🏗️ What Happens in Real Projects?

Route Handler use cases in production:

app/api/
├── webhooks/
│   ├── stripe/route.ts      # Stripe payment webhooks
│   └── github/route.ts      # GitHub CI/CD webhooks
├── revalidate/route.ts      # CMS revalidation webhook
├── upload/route.ts          # File upload endpoint
├── auth/
│   └── [...nextauth]/route.ts # NextAuth handler
└── products/
    ├── route.ts             # GET /api/products, POST /api/products
    └── [id]/route.ts        # GET/PUT/DELETE /api/products/:id

⚙️ How It Works

Route Handler request lifecycle:

  1. HTTP request arrives at /api/products
  2. Next.js matches app/api/products/route.ts
  3. Calls the exported function matching the HTTP method (GET, POST, etc.)
  4. Function receives NextRequest and a context object with params
  5. Function returns a NextResponse or plain Response
  6. Response is sent to the client

Caching: GET Route Handlers are cached by default in Next.js 14. In Next.js 15, they are not cached by default. Use export const dynamic = 'force-dynamic' to opt out of caching.

💻 Example

Route Handler Patterns — CRUD Route Handler with auth, validation, and error handling.

ts
// app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { requireAuth } from '@/lib/auth';
import { z } from 'zod';

const CreateProductSchema = z.object({
  name: z.string().min(1),
  price: z.number().positive(),
  category: z.string(),
});

// GET /api/products
export async function GET(request: NextRequest) {
  const { searchParams } = request.nextUrl;
  const category = searchParams.get('category');

  const products = await db.product.findMany({
    where: category ? { category } : undefined,
    orderBy: { createdAt: 'desc' },
  });

  return NextResponse.json(products);
}

// POST /api/products
export async function POST(request: NextRequest) {
  // Auth check
  const session = await requireAuth(request);
  if (!session) {
    return NextResponse.json({ error: 'Unauthorised' }, { status: 401 });
  }

  // Parse and validate body
  const body = await request.json();
  const result = CreateProductSchema.safeParse(body);
  if (!result.success) {
    return NextResponse.json(
      { error: 'Validation failed', issues: result.error.issues },
      { status: 400 }
    );
  }

  const product = await db.product.create({ data: result.data });
  return NextResponse.json(product, { status: 201 });
}

🤔 Common Confusions

❓ Should I use Route Handlers or Server Actions for form submissions?

Server Actions for forms in your own app — they are simpler, type-safe, and integrate with React's transition API. Route Handlers for forms submitted from external sources (mobile apps, third-party services) or when you need a traditional REST endpoint.

❓ Can I use Route Handlers to fetch data for Server Components?

Technically yes, but it is an anti-pattern. Calling your own API from a Server Component adds an unnecessary network hop (server → your API → server). Instead, call the data function directly: import { getProducts } from "@/lib/data" and call it in the Server Component.

❓ What is the difference between route.ts and page.tsx in the same folder?

They cannot coexist in the same folder. page.tsx creates a UI route (returns JSX). route.ts creates an API endpoint (returns Response). If you need both, put the API in a subfolder: app/products/route.ts for the API and app/products/page.tsx for the UI.

🚨 Common Production Issues

CORS errors on Route Handlers — Route Handlers do not add CORS headers by default. Add them manually in the response headers or use a middleware that adds CORS headers for /api/* routes.

Forgetting to verify webhook signatures — an unverified webhook endpoint accepts requests from anyone. Always verify the signature using the provider's SDK (Stripe, GitHub, etc.).

Returning Node.js res.json() instead of NextResponse.json() — Route Handlers use the Web API, not Node.js. Using the wrong API causes runtime errors.

✅ Best Practices

Use NextRequest and NextResponse — they extend the Web API with Next.js-specific features

Always validate request bodies with Zod or similar — never trust client input

Verify webhook signatures before processing — never process unverified webhooks

Return appropriate HTTP status codes — 201 for created, 204 for deleted, 400 for validation errors

Add CORS headers for endpoints consumed by external clients

🏛️ Architect's Note

Senior Architect's Perspective

Route Handlers should be the thin API layer in a Next.js app — they validate input, check auth, call business logic functions, and return responses. Business logic belongs in lib/, not in route handlers. A route handler should be 20–30 lines maximum. If it is longer, extract the logic.

💬 Frequently Asked Questions

How do I add middleware to Route Handlers (e.g., rate limiting)?

Create a higher-order function that wraps your handler: const withRateLimit = (handler) => async (req, ctx) => { await checkRateLimit(req); return handler(req, ctx); }. Then export const GET = withRateLimit(myHandler). Or use middleware.ts for app-wide rate limiting.

Can Route Handlers stream responses?

Yes — return a Response with a ReadableStream for streaming. This is used for Server-Sent Events and streaming AI responses: return new Response(stream, { headers: { "Content-Type": "text/event-stream" } }). The Vercel AI SDK uses this pattern.

How do I handle file uploads in Route Handlers?

Use request.formData() to parse multipart form data: const formData = await request.formData(); const file = formData.get("file") as File; const buffer = await file.arrayBuffer(). Then upload to S3 or another storage service.

🎯 Key Takeaways

1Route Handlers replace pages/api/ — defined in route.ts files inside app/
2Export named functions matching HTTP methods: GET, POST, PUT, DELETE
3Use NextRequest/NextResponse — they extend the Web API
4Do NOT use Route Handlers for internal Server Component data fetching — call functions directly
5Always validate input with Zod and verify webhook signatures
6Route Handlers are the API layer for external consumers, webhooks, and file uploads

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Next.js references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide Next.js learning path

Browse the full Next.js topic collection and learning order on SplashRide.

Next.js Interview Prep

Practice Next.js interview questions and scenario-based answers.

Frontend Engineer Career Path

A beginner-friendly journey through web foundations, React, production frontend engineering, and interview preparation.

Full Stack Java Developer Career Path

A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.

Building REST APIs

Build production-grade REST APIs in Next.js — versioning, error handling, authentication, rate limiting, and the patterns used in enterprise applications.

Middleware

Master Next.js Middleware — the edge function that runs before every request, enabling auth redirects, A/B testing, geolocation, and request modification at the CDN edge.