Vue 3 项目一旦把非页面逻辑放到 Supabase Edge Functions 上,函数目录通常会很快膨胀:CORS 配置散落在每个函数入口,鉴权逻辑复制多份,Vue 3 侧调用地址和请求头硬编码。这篇文章把边缘函数当作独立模块来工程化,从目录结构、共享模块、前端封装到部署链路一次说清。

一、先建立可复用的函数目录与配置
工程化的第一步不是马上写业务代码,而是把每个边缘函数都需要的横切能力抽出来。Supabase CLI 初始化项目后,根目录会出现 supabase/functions 目录,建议在其中新建 _shared 文件夹。下划线前缀在部署时通常不会被当作独立函数入口,适合放置 cors.ts、auth.ts、response.ts 等模块。这样 hello/index.ts 和 create-checkout/index.ts 等函数入口只保留业务主流程,避免每个文件都出现一份复制粘贴的跨域头。
supabase/
functions/
_shared/
cors.ts
auth.ts
response.ts
hello/
index.ts
create-checkout/
index.ts
依赖管理方面,Edge Functions 运行在 Deno 环境,推荐使用 deno.json 或 import_map.json 固定依赖来源。比如把 @supabase/supabase-js 映射到 esm.sh 的稳定版本,函数里就可以用裸模块名导入,而不是写一长串远程 URL。这样后续升级版本时只需要改一处配置。
{
"imports": {
"@supabase/supabase-js": "https://esm.sh/@supabase/supabase-js@2"
}
}
Supabase 的 config.toml 也需要配合工程化。verify_jwt 可以控制某个函数是否强制校验用户身份,开发阶段一般对需要登录态的函数开启。权限策略不要与前端路由混淆,边缘函数仍然要通过服务端 API 访问数据库,而不是直接信任请求体里的用户字段。
[functions.hello] verify_jwt = true [functions.create-checkout] verify_jwt = true
二、把 CORS、鉴权与错误处理抽成共享模块
跨域问题在浏览器端表现得最明显,预检请求 OPTIONS 如果处理不当,Vue 3 页面会直接抛错。与其让每个函数自己处理,不如把 CORS 头集中到一个文件。下面的 cors.ts 同时负责返回预检响应,函数入口只需要调用一次。
export const corsHeaders = {
"Access-Control-Allow-Origin": Deno.env.get("CLIENT_ORIGIN") ?? "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type",
};
export function handleCors(req: Request) {
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
}
CLIENT_ORIGIN 放在环境变量中,本地调试可以指向 http://localhost:5173,生产环境再改成正式域名。不要在函数里写死多个允许来源,否则每加一个前端环境就要重新部署。若业务确实需要多来源,可以读取环境变量后用逗号拆分,再判断当前请求是否匹配。
Edge Functions 不能因为部署在服务端就默认请求可信。Supabase 客户端传来的 Authorization 或 apikey 都需要在函数侧再次验证。共享 auth.ts 可以封装 getUser 逻辑,从请求头取出 token,再调用 Supabase 服务端 API 获取用户。注意这里要用服务端角色密钥初始化客户端,不能把前端 anon key 当作唯一凭据。
import { createClient } from "@supabase/supabase-js";
export async function requireUser(req: Request) {
const authHeader = req.headers.get("Authorization") ?? "";
const token = authHeader.replace("Bearer ", "");
const supabase = createClient(
Deno.env.get("SUPABASE_URL") ?? "",
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
{
auth: {
autoRefreshToken: false,
persistSession: false,
},
}
);
const { data, error } = await supabase.auth.getUser(token);
if (error || !data.user) {
return null;
}
return data.user;
}
统一返回结构能显著降低 Vue 3 侧处理接口的复杂度。比如所有函数都返回 JSON 形状,并且自动带上 CORS 头。下面这个 json 函数接收未知数据和 ResponseInit,避免业务函数重复写 Content-Type 与跨域配置。
import { corsHeaders } from "./cors.ts";
export function json(data: unknown, init: ResponseInit = {}) {
return new Response(JSON.stringify(data), {
...init,
headers: {
...corsHeaders,
"Content-Type": "application/json",
...init.headers,
},
});
}
例如 hello 函数可以只关注处理流程:先处理预检,再做用户校验,最后返回业务数据。共享模块加上之后,入口文件会短很多。
import { handleCors } from "../_shared/cors.ts";
import { requireUser } from "../_shared/auth.ts";
import { json } from "../_shared/response.ts";
Deno.serve(async (req) => {
const preflight = handleCors(req);
if (preflight) return preflight;
const user = await requireUser(req);
if (!user) {
return json({ message: "未登录或令牌无效" }, { status: 401 });
}
return json({
user_id: user.id,
email: user.email,
});
});
三、Vue 3 侧统一封装调用与类型同步
Vue 3 项目中,推荐直接使用 @supabase/supabase-js 提供的 functions.invoke,而不是手写 fetch 处理会话、apikey 和返回结构。Supabase 客户端会从当前登录用户自动附加 Authorization 头,前端只需关心业务载荷和错误分支。把调用逻辑收敛到 src/lib/edge.ts 之后,组件中不会出现重复的 URL 拼接。
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY
);
export async function invokeEdge(
name: string,
body?: Record<string, unknown>
) {
const { data, error } = await supabase.functions.invoke(name, { body });
if (error) {
throw new Error(error.message);
}
return data;
}
进一步可以封装一个 useEdgeFunction 组合式函数,把 loading、result、error 三个状态统一管理。调用时传入函数名和载荷,组件里就不需要反复写 try/catch 与加载中判断。这样也能把错误提示策略集中在一处,例如网络失败时展示统一文案。
import { ref } from "vue";
import { invokeEdge } from "./edge";
export function useEdgeFunction() {
const loading = ref(false);
const result = ref<Record<string, unknown> | null>(null);
const error = ref<string | null>(null);
async function run(name: string, payload?: Record<string, unknown>) {
loading.value = true;
error.value = null;
try {
result.value = await invokeEdge(name, payload);
} catch (err) {
error.value = err instanceof Error ? err.message : "调用失败";
} finally {
loading.value = false;
}
}
return { loading, result, error, run };
}
Edge Functions 的返回值是 any 类型时,Vue 组件很难获得完整提示。可以在工程中维护一个 edge-types.ts 文件,把每个函数的响应结构声明出来。如果团队能接受手动同步,至少为关键业务函数建立 interface;如果函数数量多,可以考虑用 Supabase 的类型生成能力配合脚本,把 OpenAPI 或自定 schema 转成前端可导入类型。手动维护时不要直接在组件里写 Record<string, unknown>,而应该通过泛型参数给 invokeEdge 指定返回类型,让调用处获得具体的 data 字段提示。
export interface HelloResult {
user_id: string;
email: string;
}
export async function invokeEdge<T>(
name: string,
body?: Record<string, unknown>
): Promise<T> {
const { data, error } = await supabase.functions.invoke<T>(name, { body });
if (error) {
throw new Error(error.message);
}
return data as T;
}
四、本地调试与 CI 自动部署
Supabase CLI 支持在本地启动边缘函数,命令通常是 supabase functions serve。若函数读取环境变量,可以用 --env-file 指定本地文件。这个命令会基于 Deno 提供热更新,前端 Vue 3 开发服务器可以直接把请求发到本地方便联调。例如在 package.json 中配置并行脚本,用 concurrently 同时启动 Vite 和 functions serve。
supabase functions serve --env-file ./supabase/.env.local
共享模块是测试的重点。Deno 自带测试运行器,直接给 _shared 下模块写单测,不必每次部署之后才发现 CORS 配置少了一个 header。测试命令 deno test supabase/functions/_shared 可以纳入 pre-commit 或 CI。如果函数依赖环境变量,测试前先通过 Deno.env.set 设置好模拟值。
Deno.test("cors headers include json content type", () => {
const headers = corsHeaders;
if (!headers["Access-Control-Allow-Origin"]) {
throw new Error("缺少跨域来源配置");
}
});
部署环节建议绑定 Supabase 的 access token 和项目引用,不要使用个人账号交互式登录来跑 CI。GitHub Actions 中先安装 Supabase CLI,再执行 supabase functions deploy 命令。每个函数可以单独部署,也可以部署全部。只部署变更函数可以缩短发布时间,并减少误发布风险。
name: Deploy Edge Functions
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: supabase/setup-cli@v1
with:
version: latest
- run: supabase functions deploy hello
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
SUPABASE_PROJECT_REF: ${{ secrets.SUPABASE_PROJECT_REF }}
还要把环境变量提前配置到 Supabase 项目的 Edge Functions 管理界面或 CLI 中,生产环境至少需要 CLIENT_ORIGIN、SUPABASE_URL、SUPABASE_SERVICE_ROLE_KEY。CLIENT_ORIGIN 不要直接填通配符,尤其涉及登录态接口时,明确允许来源能降低跨站调用风险。对 Vue 3 前端来说,正式环境的 VITE_SUPABASE_URL 和 VITE_SUPABASE_ANON_KEY 也需要同步更新,避免函数部署成功但前端仍在请求旧项目。
Supabase Edge FunctionsVue 3 边缘函数Deno 部署修改时间:2026-10-03 21:25:05