一份Route Handler省下的三天 - 用Next.js代理网关请求,前端只写一次调用
为什么要用代理网关请求?
独立开发者接AI功能时,最常见的写法是前端直接调模型API。这个写法有个致命问题:API Key要么硬编码进前端代码,要么每次换模型都改客户端。我见过一个小团队为了接入三个模型,前端代码里维护了三套调用逻辑,每次切换供应商都要发版,前后折腾了三天。
把这些时间压缩到半小时的办法很简单:在Next.js的Route Handler里写一个代理网关,前端只面对一个稳定的内部端点,供应商的差异、Key的管理、日志记录全部收敛在服务端。下面是改造前后的对比:
| 项目 | 改造前(前端直连) | 改造后(Route Handler代理) |
|---|---|---|
| API Key暴露风险 | 高,打包进客户端 | 零暴露 |
| 更换模型供应商 | 改前端+发版,约2小时/次 | 改服务端一处,约5分钟/次 |
| 用量统计 | 各端分散,需人工汇总 | 服务端统一记录,实时可见 |
| 首次接入新模型 | 半天到一天 | 30分钟内 |
对一两个人的小团队来说,这些节省下来的时间是实打实的——尤其当你同时维护Web端和小程序端时,代理写法能把客户端代码量砍掉一半以上。
第一步:注册并获取API Key
本文以ThisToken.AI为例(它提供统一的OpenAI兼容接口,一个Key可调多个模型)。流程:
- 访问 https://api.thistoken.ai/register 注册账号;
- 进入控制台的API Keys页面,创建一个新Key并妥善保存;
- 在模型列表页确认你要调用的模型名称。价格方面不做猜测,以官网价格页为准。
第二步:配置环境变量
在项目根目录创建 .env.local(记得加入 .gitignore):
THISTOKEN_API_KEY=sk-你的key第三步:写Route Handler
创建 app/api/chat/route.ts:
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
try {
const { messages, model } = await req.json();
const upstream = await fetch("https://api.thistoken.ai/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
// Key只存在于服务端,永不下发到浏览器
Authorization: `Bearer ${process.env.THISTOKEN_API_KEY}`,
},
body: JSON.stringify({
model: model ?? "gpt-4o-mini",
messages,
}),
});
if (!upstream.ok) {
const errText = await upstream.text();
return NextResponse.json(
{ error: `上游错误: ${upstream.status}`, detail: errText },
{ status: upstream.status }
);
}
const data = await upstream.json();
// 这里可以顺手记录用量:data.usage 里有 prompt_tokens / completion_tokens
console.log(
`[chat] model=${data.model} tokens=${data.usage?.total_tokens ?? "n/a"}`
);
return NextResponse.json({
content: data.choices?.[0]?.message?.content ?? "",
usage: data.usage,
});
} catch (e) {
return NextResponse.json({ error: "代理请求失败" }, { status: 500 });
}
}注意代码中的 https://api.thistoken.ai/v1 是base URL,所有端点都挂在它下面,兼容OpenAI的请求格式,所以你之前写过的调用逻辑基本可以直接复用。
第四步:前端调用
前端只需要面对自己的 /api/chat:
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "帮我总结这段话" }],
}),
});
const { content } = await res.json();几个值得加的增强
1. 流式响应(SSE)。 加上 "stream": true,然后用 upstream.body 作为ReadableStream直接透传给客户端,首字延迟通常能从数秒降到一秒以内:
return new NextResponse(upstream.body, {
headers: { "Content-Type": "text/event-stream" },
});2. 统一超时与重试。 用 AbortSignal.timeout(30000) 控制上游超时,避免一个慢请求拖垮整个函数实例。
3. 用量落库。 把上面那行 console.log 换成写入数据库,就是最简单的用量审计起点——以后核对账单不用再翻日志。
常见坑
- 路由文件放错位置:必须是
app/api/xxx/route.ts,导出POST/GET等对应HTTP方法的小写函数。 - 环境变量没生效:修改
.env.local后要重启 dev server。 - 把Key透传给了前端:任何情况下都不要把
THISTOKEN_API_KEY通过响应体返回或注入客户端组件。 - 错误处理太薄:上游返回429或5xx时,给前端一个明确的错误码,排查时间能省下不少。
小结
代理网关写法的一次性投入不到一小时,换来的是:Key零暴露、换供应商从“发版级”操作降为改一个字符串、用量数据在一处集中可见。对独立开发者来说,这是少有的“投入小、复利大”的架构决策。
如果你还没注册ThisToken.AI,可以先到 https://api.thistoken.ai/register 创建账号,拿到Key后把上面的代码跑起来,半小时内你就能看到第一段返回结果。
---
想直接跑通示例?访问 https://api.thistoken.ai/register 注册 ThisToken.AI,获取 API Key 后即可开始。