Route Handlers

Route Handlers 让你在 App Router 里直接基于 Web 标准的 Request / Response API 写接口。它们是 pages/api/* 的现代替代品,不需要额外的 API Routes。

约定

app 目录任意位置新建 route.ts(或 .js),导出对应 HTTP 方法的函数:

// app/api/hello/route.ts
export async function GET(request: Request) {
  return Response.json({ hello: 'world' });
}

支持的方法:GETPOSTPUTPATCHDELETEHEADOPTIONS。未实现的方法会自动返回 405 Method Not Allowed

约束:同一个路由段下,route.tspage.tsx 不能共存。

使用 NextRequest / NextResponse

Next.js 在原生 API 上扩展了更便捷的工具:

import { NextRequest, NextResponse } from 'next/server';

export async function GET(req: NextRequest) {
  const q = req.nextUrl.searchParams.get('q') ?? '';
  return NextResponse.json({ query: q });
}

缓存

Route Handlers 默认不缓存。想让 GET 缓存?在文件里导出:

export const dynamic = 'force-static';

export async function GET() {
  const res = await fetch('https://data.mongodb-api.com/…', {
    headers: { 'API-Key': process.env.DATA_API_KEY! },
  });
  return Response.json({ data: await res.json() });
}

其它 HTTP 方法(POST 等)即使和 GET 在同一个文件里也不会被缓存。

开启 Cache Components 后

启用 cacheComponents: true 后,GET Route Handlers 与页面遵循同一套渲染模型:

  • 不访问运行时 API、没有非确定性操作时会在构建期预渲染。
  • 访问 headers() / cookies() / Math.random() / 请求对象属性时自动下沉到请求时。
  • 需要缓存数据库查询等耗时操作时,把它抽成带 'use cache' 的帮助函数。
import { cacheLife } from 'next/cache';

export async function GET() {
  const products = await getProducts();
  return Response.json(products);
}

async function getProducts() {
  'use cache';
  cacheLife('hours');
  return db.query('SELECT * FROM products');
}

'use cache' 不能直接放在 handler 函数体里,必须放到帮助函数。

用 TypeScript 类型化参数

Next.js 内置 RouteContext 帮你拿到强类型的动态段:

// app/users/[id]/route.ts
import type { NextRequest } from 'next/server';

export async function GET(_req: NextRequest, ctx: RouteContext<'/users/[id]'>) {
  const { id } = await ctx.params;
  return Response.json({ id });
}

类型会在 next dev / next build 时自动生成。

典型用法

表单或 Webhook

export async function POST(req: Request) {
  const data = await req.json();
  // 处理 webhook / 写库…
  return new Response('ok');
}

流式响应

直接返回 ReadableStream

export async function GET() {
  const stream = new ReadableStream({
    async start(controller) {
      controller.enqueue('hello ');
      controller.enqueue('world');
      controller.close();
    },
  });
  return new Response(stream, { headers: { 'Content-Type': 'text/plain' } });
}

特殊 Route Handlers

sitemap.tsopengraph-image.tsxicon.tsx 这些特殊文件也都是 Route Handler,默认静态化,除非使用了运行时 API 或显式配置成动态。

何时用 Route Handler、何时用 Server Action?

场景 选哪个
内部表单、UI 触发的写操作 Server Action
外部集成(webhook、第三方 OAuth 回调) Route Handler
给移动端 / 第三方客户端提供 API Route Handler
需要严格的 HTTP 方法语义 Route Handler

总体原则:面向自家 UI 用 Server Action,面向外部或需要纯 HTTP 语义时用 Route Handler。

Route Handlers

Route Handlers 讓你在 App Router 直接基於 Web 標準的 Request / Response API 撰寫 API。它們是 pages/api/* 的現代替代品,不需再搭配 API Routes。

約定

app 目錄任意位置新建 route.ts(或 .js),匯出對應 HTTP 方法的函式:

// app/api/hello/route.ts
export async function GET(request: Request) {
  return Response.json({ hello: 'world' });
}

支援方法:GETPOSTPUTPATCHDELETEHEADOPTIONS。未實作的方法會自動回傳 405 Method Not Allowed

限制:同一路由段下,route.tspage.tsx 不可共存。

使用 NextRequest / NextResponse

Next.js 在原生 API 之上擴充了更便捷的工具:

import { NextRequest, NextResponse } from 'next/server';

export async function GET(req: NextRequest) {
  const q = req.nextUrl.searchParams.get('q') ?? '';
  return NextResponse.json({ query: q });
}

快取

Route Handlers 預設不快取。若要 GET 被快取,可在檔案中匯出:

export const dynamic = 'force-static';

export async function GET() {
  const res = await fetch('https://data.mongodb-api.com/…', {
    headers: { 'API-Key': process.env.DATA_API_KEY! },
  });
  return Response.json({ data: await res.json() });
}

其他 HTTP 方法(POST 等)即使與 GET 在同一檔案,也不會被快取。

啟用 Cache Components 後

cacheComponents: true 時,GET Route Handlers 與頁面遵循同一套渲染模型:

  • 不存取執行時 API、也沒有非確定性操作時,會於建置期預渲染。
  • 存取 headers() / cookies() / Math.random() / 請求物件屬性時,自動下沉到請求時。
  • 若需快取資料庫查詢等耗時操作,將其抽成帶 'use cache' 的輔助函式。
import { cacheLife } from 'next/cache';

export async function GET() {
  const products = await getProducts();
  return Response.json(products);
}

async function getProducts() {
  'use cache';
  cacheLife('hours');
  return db.query('SELECT * FROM products');
}

'use cache' 不能直接寫在 handler 函式本體內,必須置於輔助函式。

以 TypeScript 型別化參數

Next.js 內建 RouteContext 幫你取得強型別動態段:

// app/users/[id]/route.ts
import type { NextRequest } from 'next/server';

export async function GET(_req: NextRequest, ctx: RouteContext<'/users/[id]'>) {
  const { id } = await ctx.params;
  return Response.json({ id });
}

型別會於 next dev / next build 時自動生成。

典型用法

表單或 Webhook

export async function POST(req: Request) {
  const data = await req.json();
  // 處理 webhook / 寫入資料庫…
  return new Response('ok');
}

串流回應

直接回傳 ReadableStream

export async function GET() {
  const stream = new ReadableStream({
    async start(controller) {
      controller.enqueue('hello ');
      controller.enqueue('world');
      controller.close();
    },
  });
  return new Response(stream, { headers: { 'Content-Type': 'text/plain' } });
}

特殊 Route Handlers

sitemap.tsopengraph-image.tsxicon.tsx 這些特殊檔案亦屬 Route Handler,預設靜態化,除非使用執行時 API 或明確設為動態。

何時用 Route Handler,何時用 Server Action?

情境 建議
內部表單、UI 觸發的寫入 Server Action
外部整合(webhook、第三方 OAuth 回呼) Route Handler
提供行動端/第三方客戶端 API Route Handler
需嚴格 HTTP 方法語意 Route Handler

原則:面向自家 UI 用 Server Action,面向外部或需純 HTTP 語意時用 Route Handler。

Route Handlers

Route Handlers let you build API endpoints directly in the App Router using the standard Request / Response APIs. They replace the legacy pages/api/* — no need to mix the two.

Convention

Add a route.ts (or .js) anywhere under app/ and export functions named after HTTP methods:

// app/api/hello/route.ts
export async function GET(request: Request) {
  return Response.json({ hello: 'world' });
}

Supported methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. Unimplemented methods return 405 Method Not Allowed.

Constraint: route.ts and page.tsx can't coexist at the same segment.

NextRequest / NextResponse

Next.js extends the standard primitives with convenience helpers:

import { NextRequest, NextResponse } from 'next/server';

export async function GET(req: NextRequest) {
  const q = req.nextUrl.searchParams.get('q') ?? '';
  return NextResponse.json({ query: q });
}

Caching

Route Handlers are not cached by default. Opt in for GET by exporting:

export const dynamic = 'force-static';

export async function GET() {
  const res = await fetch('https://data.mongodb-api.com/…', {
    headers: { 'API-Key': process.env.DATA_API_KEY! },
  });
  return Response.json({ data: await res.json() });
}

Other HTTP methods (POST, etc.) aren't cached even if they share a file with a cached GET.

With Cache Components

When cacheComponents: true is enabled, GET handlers follow the same rendering model as pages:

  • Prerendered at build time if they don't touch runtime APIs or non-deterministic operations.
  • Deferred to request time when they do (headers(), cookies(), Math.random(), request props…).
  • To cache expensive work (DB queries), extract it into a helper with 'use cache':
import { cacheLife } from 'next/cache';

export async function GET() {
  const products = await getProducts();
  return Response.json(products);
}

async function getProducts() {
  'use cache';
  cacheLife('hours');
  return db.query('SELECT * FROM products');
}

'use cache' can't sit directly in the handler body — always extract it.

Typing params in TypeScript

Use the built-in RouteContext helper for strongly-typed dynamic segments:

// app/users/[id]/route.ts
import type { NextRequest } from 'next/server';

export async function GET(_req: NextRequest, ctx: RouteContext<'/users/[id]'>) {
  const { id } = await ctx.params;
  return Response.json({ id });
}

Types are generated during next dev / next build.

Common patterns

Forms or webhooks

export async function POST(req: Request) {
  const data = await req.json();
  // process webhook / write to DB…
  return new Response('ok');
}

Streaming responses

Return a ReadableStream directly:

export async function GET() {
  const stream = new ReadableStream({
    async start(controller) {
      controller.enqueue('hello ');
      controller.enqueue('world');
      controller.close();
    },
  });
  return new Response(stream, { headers: { 'Content-Type': 'text/plain' } });
}

Special Route Handlers

sitemap.ts, opengraph-image.tsx, icon.tsx, and the other metadata files are Route Handlers too — static by default, and become dynamic only if they use runtime APIs or explicit dynamic config.

Route Handler or Server Action?

Situation Pick
Internal forms, UI-driven mutations Server Action
External integrations (webhooks, OAuth) Route Handler
APIs for mobile or third-party clients Route Handler
You need strict HTTP method semantics Route Handler

Rule of thumb: use Server Actions for your own UI, Route Handlers when you need a clean HTTP surface for the outside world.