CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Next.js 实战入门/课时/Route Handlers
RawBlame
课时目录12 节

Next.js 实战入门

  1. 01Next.js 是什么
  2. 02项目创建与目录
  3. 03App Router 与文件路由
  4. 04Server 与 Client 组件
  5. 05数据获取与缓存
  6. 06渲染模式 SSR / SSG / ISR
  7. 07Route Handlers
  8. 08MDX 与课时渲染
  9. 09Tailwind 与样式
  10. 10认证与登录门控
  11. 11读 platform 源码
  12. 12综合练习与部署

Route Handlers

app/api route、Request/Response、与 Spring Boot 分工。

30 分钟

Route Handlers

这一课你将能做到

  1. 在 app/api/**/route.ts 里写出 GET / POST 等接口,处理查询参数、JSON 请求体与动态段
  2. 实现一个「转发到 Spring Boot 并透传登录态」的 BFF 接口(本站 roadmap-node 接口的同款模式)
  3. 说清 Route Handler 与 Server Action 的区别,知道各自该用在什么场景

为什么要学

App Router 出现之前,Next.js 项目的后端接口要么另起一个 Express/Koa 服务,要么全部依赖外部后端。Route Handlers 把「写接口」变成了在 app/ 目录里建一个 route.ts 文件——和页面放在一起,同一套部署。

什么时候真的需要它?三个高频场景:

  1. BFF(Backend for Frontend):前端不想暴露后端真实地址,或需要在中间「加一层」(拼数据、鉴权、限流、改格式)。本站就有一个活例子:src/app/api/roadmap-node/[slug]/[nodeKey]/route.ts,它把请求转发到后端并透传用户 token。
  2. 给第三方提供回调入口:支付回调、Webhook 接收,必须是一个公网 URL。
  3. 纯小项目:个人工具、demo、内部系统,不想为几个接口起一个 Java 服务。

面试角度,「Route Handler 是什么、和 API Routes 有何区别、和 Server Action 怎么选」属于 App Router 新特性题的常客。答案核心:Route Handler 是 HTTP 层(有 URL、能收任意 HTTP 请求),Server Action 是 RPC 层(表单/组件直接调函数,无独立 URL 语义)。

先建立图像

Route Handler 的文件约定一图流:

app/api/health/route.ts          → GET /api/health
app/api/echo/route.ts            → POST /api/echo
app/api/tracks/[slug]/route.ts   → GET /api/tracks/java-essentials

route.ts 里导出的函数名 = 允许的 HTTP 方法:
  export async function GET()    → 处理 GET
  export async function POST()   → 处理 POST
  还有 PUT / PATCH / DELETE / HEAD / OPTIONS

两条铁律:

  • 文件名必须叫 route.ts(不是 page.tsx,也不是 index.ts)
  • 同一文件夹里 route.ts 和 page.tsx 不能共存(同一个 URL 冲突)

请求与响应的最小骨架:

   Request(入)                     Response(出)
┌──────────────────┐            ┌──────────────────────┐
│ request.nextUrl  │ 查询参数    │ NextResponse.json()  │ JSON 响应
│ request.headers  │ 请求头      │ NextResponse.redirect│ 跳转
│ await request.json() 请求体   │ new Response(body)   │ 自定义
│ { params }       │ 动态段      │ NextResponse.cookies │ 操作 Cookie
└──────────────────┘            └──────────────────────┘

跟着做

步骤 1:第一个 GET 接口——健康检查

新建 src/app/api/health/route.ts:

import { NextResponse } from "next/server";

export async function GET() {
  return NextResponse.json({
    status: "ok",
    time: new Date().toISOString(),
  });
}

启动 dev server 后浏览器访问 http://localhost:3000/api/health,看到 JSON。恭喜,你写了第一个跑在 Next.js 里的后端接口。

步骤 2:查询参数与 POST 请求体

src/app/api/echo/route.ts——把「你发什么」原样诊断回来:

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

// GET /api/echo?q=hello&page=2
export async function GET(request: NextRequest) {
  const q = request.nextUrl.searchParams.get("q");
  const page = request.nextUrl.searchParams.get("page") ?? "1";
  return NextResponse.json({ q, page });
}

// POST /api/echo,body: { "name": "tom" }
export async function POST(request: NextRequest) {
  const body = await request.json();
  const name = body?.name ?? "anonymous";
  return NextResponse.json({ message: `你好,${name}`, receivedAt: Date.now() });
}

用 curl 或浏览器验证:

curl "http://localhost:3000/api/echo?q=next&page=3"
curl -X POST http://localhost:3000/api/echo \
  -H "Content-Type: application/json" \
  -d '{"name":"tom"}'

要点:查询参数用 request.nextUrl.searchParams;请求体必须 await request.json()(读取是异步的)。参数校验在生产里建议上 zod 之类的库,先手写判断也行。

步骤 3:动态段接口

src/app/api/tracks/[slug]/route.ts:

import { NextResponse } from "next/server";

const DB: Record<string, { slug: string; title: string }> = {
  "java-essentials": { slug: "java-essentials", title: "Java 基础" },
  "nextjs-essentials": { slug: "nextjs-essentials", title: "Next.js 基础" },
};

export async function GET(
  _request: Request,
  { params }: { params: Promise<{ slug: string }> }
) {
  const { slug } = await params; // Next 15:params 是 Promise
  const track = DB[slug];
  if (!track) {
    return NextResponse.json({ error: "课程不存在" }, { status: 404 });
  }
  return NextResponse.json(track);
}

访问 /api/tracks/java-essentials 得到数据,访问不存在的 slug 得到 404。注意第二个函数参数的位置——params 在第二个参数里,和页面组件一致(同样 await)。

步骤 4:BFF 转发接口——本站同款模式

场景:浏览器不该直接知道 Spring Boot 的地址,且后端需要用户 token 做鉴权。写一个代理层:

// src/app/api/progress/route.ts
import { NextRequest, NextResponse } from "next/server";

const BACKEND = process.env.API_INTERNAL_URL ?? "http://localhost:8080";

export async function GET(request: NextRequest) {
  // 1. 透传登录态:把浏览器带来的 token 转给后端
  const token = request.cookies.get("token")?.value;
  const search = request.nextUrl.search; // 保留查询参数
  // 2. 服务端到服务端转发(走内网地址)
  const res = await fetch(`${BACKEND}/api/progress${search}`, {
    headers: token ? { Authorization: `Bearer ${token}` } : {},
    cache: "no-store", // 用户数据绝不缓存
  });
  // 3. 原样回传状态码与内容
  const data = await res.json().catch(() => ({}));
  return NextResponse.json(data, { status: res.status });
}

前端调用时只知道自己域名下的 /api/progress,后端地址、内网拓扑、token 机制全部封装在服务端。本站 platform 的 roadmap-node 接口就是这一模式(读 cookie 里的登录态 → fetch 后端 → 回传),第 11 课会带读源码对上号。

步骤 5:写 Cookie 的响应

登录场景常见需求——服务端把 token 写进 HttpOnly Cookie:

// src/app/api/login/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function POST(request: NextRequest) {
  const { username, password } = await request.json();
  // 真实项目:转发给后端校验,这里演示 Cookie 写法
  if (username === "tom" && password === "123456") {
    const res = NextResponse.json({ ok: true, username });
    res.cookies.set("token", "demo-jwt-token", {
      httpOnly: true,   // JS 读不到,防 XSS 窃取
      sameSite: "lax",  // 基础 CSRF 防护
      maxAge: 60 * 60 * 24 * 7,
      path: "/",
      secure: process.env.NODE_ENV === "production", // 生产必须 HTTPS
    });
    return res;
  }
  return NextResponse.json({ ok: false, message: "用户名或密码错误" }, { status: 401 });
}

httpOnly + secure + sameSite 三个属性是 Cookie 安全的标配,第 10 课认证会继续展开。

讲清楚要点

  1. Route Handler 是 HTTP 层:有 URL、有方法、有状态码,一切按 Web 标准来——Webhook、回调、公开 API、BFF 转发都找它。它接收 Web 标准 Request(Next 扩展为 NextRequest),返回 Response/NextResponse。
  2. 默认缓存语义与页面一致:用 GET 且无动态输入的 handler 在 Next 14 会被静态化(启动时执行一次);Next 15 起默认动态。需要强制动态可用 export const dynamic = "force-dynamic",或在读 request 对象后自然变动态。POST 等写方法永远动态。
  3. 和 Server Action 的分工(面试重点):Server Action 是「组件里直接调的服务端函数」,没有独立 URL,适合表单提交、页面内变更(如点赞写库);Route Handler 是「真接口」,适合回调、第三方调用、跨端复用(App 与 Web 共用)、需要 GET 语义的读取。本站两条都有:页面交互走 Server 组件取数,roadmap 节点走 Route Handler 转发。
  4. BFF 的价值不只是藏地址:聚合(一次请求拼多个后端接口)、协议转换(把 SOAP/XML 转成 JSON)、统一鉴权(token 检查集中一处)、降级(后端挂了返回友好兜底)。企业里前端团队对接口的掌控力就来自这一层。
  5. 错误返回要有语义:参数错 400、未登录 401、无权限 403、不存在 404、服务器错 500,NextResponse.json({ error }, { status }) 是标准姿势。永远别吞错误返回 200——前端没法处理。

你可能会懵的地方

  • route.ts 里的 GET 能被别人直接猜到 URL 调用吗? 公网可达就是公开的。防滥用靠:鉴权头/Cookie 校验、限流(上游 Nginx 或代码内计数)、CORS 配置(非本站域名的浏览器请求默认被浏览器拦截,但 curl 不受 CORS 限制,所以服务端校验不能省)。

  • NextResponse.json 和 new Response(JSON.stringify(...)) 有区别吗? 结果等价,前者帮你设好了 Content-Type: application/json 和序列化,还链式提供 cookies 操作。永远优先 NextResponse。

  • 为什么我的 GET handler 在 build 时就执行了? Next 14 会静态化「无动态输入的 GET」。不想这样:读一下 request.nextUrl(用了 request 即动态)或导出 export const dynamic = "force-dynamic"。Next 15 已改为默认动态,但显式声明仍是好习惯。

  • Server Action 能替代所有 Route Handler 吗? 不能。Webhook 回调(GitHub、支付)必须有公网 URL;GET 语义的接口(分享链接、RSS、App 端调用)也不适合 Action。Action 的甜区是「页面内的写操作」。

  • handler 里能读 cookies() 吗? 能。import { cookies } from "next/headers" 后 await cookies(),效果同 request.cookies。用 request 参数更直观,团队统一一种即可。

错法对照

错法问题正解
接口文件命名为 index.ts 或 api.ts不被识别为接口文件名必须是 route.ts
同目录同时放 page.tsx 和 route.ts路由冲突报错接口与页面分目录:/api/x 与 /x
用 request.query 取查询参数Request 对象没有这个属性request.nextUrl.searchParams.get("q")
所有错误都 return NextResponse.json({msg})(200)前端无法按状态码分支处理正确 status:400/401/404/500
BFF 转发时把 token 写死或硬编码后端地址泄密 & 环境不可移植从请求头/Cookie 透传 + 环境变量配置

卡点急救

  1. 405 Method Not Allowed:URL 对但方法没导出。文件里只写了 GET 却发 POST 就会 405——把对应方法的导出函数补上。
  2. POST 收到的 body 是 undefined:忘了 await request.json(),或客户端没设 Content-Type: application/json(fetch 默认 text/plain,服务端 json() 解析失败)。
  3. 转发后端时报 CORS 错:CORS 是浏览器行为,服务端到服务端的 fetch 不受影响。若报错出现在浏览器直连后端,要么后端配 CORS,要么让请求走你的 BFF 转发(这正是 BFF 的用途之一)。

面试常问

  • Route Handler 和旧版 API Routes 的区别? API Routes 属于 Pages Router(pages/api/*.ts),路由与处理函数靠默认导出对象;Route Handlers 属于 App Router(app/**/route.ts),按导出函数名映射 HTTP 方法,直接使用 Web 标准 Request/Response,支持流式响应、与页面共享同一路由树和缓存体系。新项目一律 Route Handler。

  • Route Handler 和 Server Action 怎么选? 需要公网 URL、被非浏览器调用方(Webhook、App)、GET 可分享语义、BFF 聚合转发 → Route Handler。页面内表单提交、按钮触发的服务端变更、无需独立 URL 的内部调用 → Server Action(代码量更少,自动绑定表单渐进增强)。

  • 说一个你写过的 BFF 接口,它解决了什么问题? 参考回答:学习平台有一个 /api/roadmap-node/[slug]/[nodeKey] 接口,服务端读取用户 Cookie 中的 token 后转发到内网 Spring Boot 服务,浏览器只感知同域接口。它同时解决了三件事:隐藏后端拓扑(内网地址不暴露)、集中鉴权(token 校验一处完成)、避免跨域(同源调用)。

自测三问

  1. route.ts 导出哪些函数名可以处理哪些 HTTP 方法?动态段参数从哪拿?
  2. BFF 转发接口里,token 从哪里取、转发到哪个地址、为什么不缓存?
  3. 什么需求必须用 Route Handler 而不能用 Server Action?

小结一句话

route.ts 让 Next.js 拥有了 HTTP 层:方法即导出名、params 在第二个参数、NextResponse 管输出;BFF 转发是它在企业项目里最实用的形态。

延伸

  • Next.js 官方文档:Route Handlers
  • 下一课:本站的课时内容是怎么渲染出来的——MDX。
加载本节练习…
← 上一课渲染模式 SSR / SSG / ISR下一课 →MDX 与课时渲染