Next.js + FastAPI 服务绑定(Service Bindings)实战:用 vercel.json 构建内部后端服务
【免费下载链接】examplesEnjoy our curated collection of examples and solutions. Use these patterns to build your own robust and scalable applications.项目地址: https://gitcode.com/GitHub_Trending/examples1/examples
导读
本文基于开源仓库services/nextjs-fastapi-bindings示例,讲解如何在 Vercel Services 架构中同时部署 Next.js 前端与 FastAPI 后端,并通过service bindings(服务绑定)将后端地址以环境变量BACKEND_URL的形式注入前端,实现"后端不对外暴露、仅由前端 API Route 内部代理调用"的经典前后端分离模式。读完本文,你将掌握vercel.json中services、bindings、rewrites三者的配置关系,以及 Next.js Route Handler 转发请求到 FastAPI 的完整链路。
一、示例概览:两个服务、一条私有链路
该示例是一个极简的 Vercel Services 演示,包含两个服务:
- frontend(Next.js):挂载在
/,对外提供页面与 API Route; - backend(FastAPI):仅内部可达,不直接对外暴露任何路径。
它演示了三件事:
- 一个公开的Next.js API 路由
/api/hello; - 一个不对外公开的 FastAPI 后端路由(
/status、/items等); - 通过
vercel.json中的bindings实现服务间的内部调用。
核心链路是:浏览器 → Next.js API Route(/api/backend/...)→ 环境变量BACKEND_URL→ 内部 FastAPI 服务。这样后端无需公网入口,减少了攻击面,也把"谁可以访问后端"的决策权收敛到前端代码层。
二、项目结构
仓库中该示例的完整目录结构如下(见 services/nextjs-fastapi-bindings):
nextjs-fastapi-bindings/ ├── backend/ │ ├── main.py │ ├── pyproject.toml │ └── uv.lock ├── frontend/ │ ├── app/ │ │ ├── api/backend/[[...path]]/route.js │ │ ├── api/hello/route.js │ │ ├── globals.css │ │ ├── layout.js │ │ └── page.js │ ├── next.config.js │ ├── package.json │ └── pnpm-lock.yaml └── vercel.json两个明显的工程要点:
frontend/app/api/backend/[[...path]]/route.js使用 Next.js 的catch-all 动态路由段,把/api/backend/*下的任意子路径统一转发给 FastAPI;backend/除了main.py与pyproject.toml,还带有uv.lock,说明后端依赖由 uv 管理(仓库内以uv.lock锁定依赖版本)。
三、Services 配置详解:vercel.json 是编排核心
整个架构的"总开关"在仓库根目录的 vercel.json,完整内容如下:
{ "$schema": "https://openapi.vercel.sh/vercel.json", "services": { "frontend": { "root": "frontend/", "framework": "nextjs", "bindings": [ { "type": "service", "service": "backend", "format": "url", "env": "BACKEND_URL" } ] }, "backend": { "root": "backend/", "entrypoint": "main:app" } }, "rewrites": [ { "source": "/(.*)", "destination": { "service": "frontend" } } ] }3.1 services:声明两个服务
services字段声明了仓库内的两个子服务,每个服务用root指定代码所在目录:
| 字段 | frontend 取值 | backend 取值 | 说明 |
|---|---|---|---|
root | frontend/ | backend/ | 服务源码所在目录 |
framework | nextjs | 未声明 | 前端显式指定框架;后端为 Python ASGI 应用,无需框架字段 |
entrypoint | 未声明 | main:app | 指定 FastAPI 实例,main是backend/main.py模块,app是该文件中的FastAPI实例对象 |
bindings | 配置了一条绑定 | 无 | 见 3.2 节 |
后端服务在 backend/main.py 中创建:
app = FastAPI( title="Next.js + FastAPI Services Demo", description="Internal backend service — accessed via Next.js API routes using a service binding", version="1.0.0" )entrypoint: "main:app"中的app正是这里创建的 FastAPI 实例。注意pyproject.toml(见 backend/pyproject.toml)要求 Python>=3.12,并依赖fastapi>=0.124.2与uvicorn>=0.38.0。
3.2 bindings:把后端地址注入为环境变量
bindings是本示例区别于"公共路由代理"方案(可对比同仓库的 services/nextjs-fastapi/vercel.json)的关键所在:
"bindings": [ { "type": "service", "service": "backend", "format": "url", "env": "BACKEND_URL" } ]各字段含义:
type: "service":绑定类型为服务绑定,即把另一个服务作为依赖注入;service: "backend":被绑定的目标服务名,必须与services中声明的键一致;format: "url":注入形式为 URL 字符串;env: "BACKEND_URL":注入到 frontend 服务中的环境变量名。
部署后,frontend 服务内即可通过process.env.BACKEND_URL拿到后端服务的内部地址。由于该地址是 Vercel 平台动态分配的,不应该在代码里硬编码,这正是绑定机制的价值。
3.3 rewrites:把所有流量先交给 frontend
"rewrites": [ { "source": "/(.*)", "destination": { "service": "frontend" } } ]该规则将根路径/(.*)的所有请求重写到 frontend 服务。也就是说,对外的入口只有 frontend,后端服务没有出现在任何 rewrite 的destination中,因此从公网无法直接访问 FastAPI。
四、后端实现:内部 FastAPI 服务
backend/main.py 是一个标准且极简的 FastAPI 应用,除根路径外提供了三个业务路由:
@app.get("/") def read_root(): return {"message": "FastAPI service is running"} @app.get("/status") def get_status(): return { "service": "backend", "framework": "fastapi", "timestamp": datetime.now(timezone.utc).isoformat(), } @app.get("/items") def get_items(): return {"items": SAMPLE_ITEMS, "count": len(SAMPLE_ITEMS)} @app.get("/items/{item_id}") def get_item(item_id: int): item = next((value for value in SAMPLE_ITEMS if value["id"] == item_id), None) if item is None: raise HTTPException(status_code=404, detail="Item not found") return {"item": item} @app.get("/{blah:path}") def fallback(blah): print(blah) return blah代码要点:
- 内置了三条示例数据(
SAMPLE_ITEMS),用于演示列表查询与按 ID 查询; /items/{item_id}使用类型注解item_id: int,由 FastAPI 自动做路径参数校验;查不到时抛出HTTPException(404);- 末尾的
/{blah:path}catch-all 路由会把未匹配的路径原样返回,可用于调试转发链路是否到达后端。
五、前端实现:API Route 充当反向代理
5.1 直接响应的 Next.js API 路由
frontend/app/api/hello/route.js 是一个完全运行在 Next.js 服务内的 Route Handler:
import { NextResponse } from "next/server"; export async function GET() { return NextResponse.json({ service: "frontend", framework: "nextjs", message: "Hello from Next.js API route", timestamp: new Date().toISOString(), }); }它不依赖任何后端,直接返回 JSON,用于验证 frontend 服务本身工作正常。
5.2 转发到 FastAPI 的代理路由
frontend/app/api/backend/[[...path]]/route.js 是整个示例的核心代理逻辑:
export async function GET(request, { params }) { const { path } = await params; const url = new URL(path ? path.join("/") : "", process.env.BACKEND_URL); url.search = request.nextUrl.search const res = await fetch(url, { cache: "no-store" }); const data = await res.json(); return Response.json(data, { status: res.status }); }逐行解读:
- catch-all 参数:
[[...path]]是 Next.js 的可选 catch-all 动态段。访问/api/backend/status时path = ["status"];访问/api/backend/items/1时path = ["items", "1"]; - 拼接内部地址:
path.join("/")还原出/status、/items/1等子路径,再以process.env.BACKEND_URL为基址构造完整 URL。这一步正是消费了vercel.json中 binding 注入的环境变量; - 透传查询参数:
url.search = request.nextUrl.search把浏览器请求的 query string 原样带到后端; - 实时转发:
fetch(url, { cache: "no-store" })禁用缓存,确保每次请求都实时打到 FastAPI(前端页面 page.js 中的调用也使用cache: "no-store",行为一致); - 透传状态码:
Response.json(data, { status: res.status })不仅回传 JSON 体,还把后端的 HTTP 状态码(如 404)一并透传给浏览器。
由此,/api/backend/*在语义上成为 FastAPI 的"命名空间化"镜像,前端代码无需关心后端真实地址。
六、前端页面与本地运行
6.1 交互页面
frontend/app/page.js 是一个客户端组件,提供三个交互入口:
- Call
/api/hello:请求 Next.js 自身的 API 路由; - Call
/api/backend/status:经代理路由访问 FastAPI 的/status; - Open
/api/backend/items:直接打开后端示例数据。
页面导航栏还提供了Status、Items两个链接(对应/api/backend/status与/api/backend/items)。返回的 JSON 会统一渲染在页面下方的响应区块中,便于直观对比"来自 frontend 的响应"与"来自 backend 的响应"字段差异。
6.2 本地运行
示例依赖 Vercel CLI,在项目根目录(即vercel.json所在目录)执行:
vercel dev启动后打开http://localhost:3000,依次访问:
/api/hello—— Next.js API 路由,返回service: "frontend";/api/backend/status—— 经 Next.js 代理路由转发到 FastAPI,返回service: "backend"及 UTC 时间戳;/api/backend/items—— 返回后端内置的三条示例数据;/api/backend/items/1—— 返回单条示例数据;访问不存在的 ID(如/api/backend/items/999)会得到 404。
本地开发时vercel dev会模拟vercel.json中的服务编排与绑定注入,因此process.env.BACKEND_URL在本地同样可用,无需手工配置环境变量。
七、与其他方案的对比与适用场景
同仓库还提供了姊妹示例 services/nextjs-fastapi:它通过 rewrite 把/svc/api公开路由到 FastAPI,后端可从公网直接访问。两者对比可以更清晰地理解 bindings 的定位:
| 维度 | nextjs-fastapi(rewrites 方案) | nextjs-fastapi-bindings(bindings 方案) |
|---|---|---|
| 后端可达性 | /svc/api对外公开 | 仅内部可达,无公网入口 |
| 后端地址获取 | 由路由规则固定 | 通过BACKEND_URL环境变量注入 |
| 适合场景 | 需要独立域名/路径访问后端的场景 | 后端仅服务前端、希望收敛攻击面的场景 |
如果你的 FastAPI 只是给同一个 Next.js 应用提供数据接口、不需要被第三方直接调用,bindings 方案是更安全的默认选择;后端地址由平台注入,也避免了在代码或构建配置中硬编码。
八、小结
通过services/nextjs-fastapi-bindings这个最小示例,你可以完整掌握 Vercel Services 的三种核心编排能力:
services:声明 frontend 与 backend 两个子服务及其入口(entrypoint: "main:app");bindings:把后端以 URL 形式绑定为前端的BACKEND_URL环境变量,实现内部调用;rewrites:把对外流量全部导向 frontend,确保 FastAPI 不暴露在公网。
再加上 Next.js 的 catch-all Route Handler 作为转发层,即可在几行代码内搭建一套"前端对外、后端内聚"的多语言微服务架构。如需对照公开路由方案的差异,可继续阅读同仓库的 services/nextjs-fastapi 示例。
【免费下载链接】examplesEnjoy our curated collection of examples and solutions. Use these patterns to build your own robust and scalable applications.项目地址: https://gitcode.com/GitHub_Trending/examples1/examples
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考