Vercel是一个能免费托管Web站点的云平台,相比于Github Pages,Vercel的功能要更强大得多,Vercel上不仅可以部署静态站点,它还提供了CI/CD、多环境、Edge Functions,最近还新增了存储服务,也可以免费使用。相比于完整的Linux主机虽然还有诸多限制,但部署一些轻量级的应用程序是完全够用的。
此外Vercel团队对开源社区的贡献也很多,NextJS和TurboPack都是Vercel的知名开源项目。
参考文档:https://vercel.com/docs/getting-started-with-vercel
Vercel最基础的使用方式就是部署静态站点,Vercel对大部分常见的前端框架构建均有支持,而且还十分大方的提供了100GB/月的免费流量,足够我们使用了。
在Vercel平台中直接点击Add New -> Project
创建新工程,在这里我们需要关联我们的Github、Gitlab或Bitbucket账号,关联的账号是通过OAuth授权的,因此即使私有库也可以在Vercel部署。如果我们还没想好写点什么部署到Vercel,也可以看一看右边的工程模板,里面有很多现成的例子。
在工程配置界面,我们需要填写项目名、所使用的框架以及工程根目录,后面还可以选填工程的构建命令、输出目录之类的信息,对于主流框架这里都已经填好了默认值,但如果使用非Vercel预置的框架可能需要手动填写。
填好所有信息并提交后,Vercel会自动拉取代码和编译构建,我们稍等一会就可以访问部署好的工程了。Vercel对部署的工程提供了默认的子域名,我们也可以在工程设置中绑定自己的域名,在Vercel上配置好我们的自定义域名后在DNS记录中添加CNAME即可。
参考文档:https://vercel.com/docs/functions/edge-functions
Vercel不仅仅是个静态站点托管服务,我们还可以用Edge Functions实现完整的服务端功能,不过这里需要用到NextJS框架(如果使用非NextJS框架也可以实现服务端功能,这部分可以参考Serverless Functions相关的文档)。
要使用Edge Functions我们需要在Vercel平台创建工程并和Git仓库关联,然后将一些环境变量拉取到本地。
npx vercel env pull .env.development.local
Edge Functions的用法没有什么要额外介绍的,因为NextJS本身就提供了服务端API的开发能力,我们正常使用NextJS开发,部署到Vercel后,NextJS的RSC、API等其实都是直接运行在Edge Runtime中的。
不过这里要注意Vercel的Edge Runtime和NodeJS运行时有一点区别,我就被小小的坑过一把,当时开发的一个小工具为了省去服务端存Session的麻烦打算用JWT认证鉴权,于是使用了NodeJS的jsonwebtoken
库,然而在Vercel上却不能运行,这是因为Edge Runtime缺少一些crypto
的实现,jsonwebtoken
库用到的一个加密算法不支持。
另外,Vercel的Edge Functions和Serverless Functions使用起来开发体验肯定不如SpringBoot、ASPNetCore、Laravel、Django之类的成熟服务端框架,它有诸多的限制,API设计的也缺少很多人性化的封装,总而言是就是用起来比较“生硬”,我们也完全可以仅用Vercel实现前端,服务端仍然部署在我们自己的服务器上。
参考文档:https://vercel.com/docs/storage
Vercel在早先没有提供服务端存储功能,因此通常是配合Firebase、Supabase或是自己的服务器作为后端使用的,但最近Vercel也提供了几种存储组件:KV是一个键值对数据库,基于Redis实现,Postgres是一个Serverless版的PostgreSQL,Blob是一个对象存储系统,Edge Config是一个配置中心。
使用Storage存储我们需要先创建Vercel工程和对应的存储组件实例,然后将它们关联到一起,之后还需要拉取环境变量配置,这样我们本地调试时也能连接到Vercel平台的存储组件上。
npx vercel env pull .env.development.local
这些存储组件都提供了免费使用额度,不过这里Vercel就有点抠门了_(:з)∠)_(尤其是Blob),开发阶段调试时千万别传太大的数据,否则瞬间用光免费额度就没法继续开发了。
KV的用法非常简单,和大多数Redis客户端类似,我们首先安装相关npm依赖,引入后调用get()
、set()
之类的方法就好。
npm i @vercel/kv
引入kv
客户端。
import { kv } from "@vercel/kv";
存储一个值到KV。
await kv.set("tables-inited", true);
在管理界面中,我们可以执行Redis命令对键值对进行操作。
使用Postgres需要编写SQL语句,这里注意我们平时可能MySQL使用的多一些,PostgreSQL的语法和MySQL大部分是兼容的,但也有一些小坑。
npm i @vercel/postgres
引入Postgres客户端。
import { sql } from "@vercel/postgres";
引入sql
函数后,我们直接执行SQL语句就行了。
const { rows } = await sql`select count(*) from t_messages where user_id = ${user.user_id}`;
注意上面代码中虽然使用了字符串拼接,但Vercel为我们做了一些额外处理,因此上面写法没有SQL注入问题。
在管理界面我们可以执行SQL语句操作数据。
Vercel Blob是一个对象存储组件,但Vercel平台下使用方法和一般的OSS有些区别。我们平时用到OSS,上传文件可能是文件先传输到服务端然后再由服务端传输到OSS上,Vercel不推荐这种方式并限制了服务端的最大上传数据大小为4.5MB。
Vercel推荐“客户端上传”,简单来说就是我们的服务端代码只负责给浏览器前端一个Token,浏览器前端用这个Token直接将文件上传到对象存储中,上传完成后由Blob组件回调我们的服务端,通知我们上传完成。
前端代码如下。
app/avatar/upload/page.jsx
"use client";
import { upload } from "@vercel/blob/client";
import { useState, useRef } from "react";
export default function AvatarUploadPage() {
const inputFileRef = useRef(null);
const [blob, setBlob] = useState(null);
return (
<>
<h1>Upload Your Avatar</h1>
<form
onSubmit={async (event) => {
event.preventDefault();
const file = inputFileRef.current.files[0];
const newBlob = await upload(file.name, file, {
access: "public",
handleUploadUrl: "/api/avatar/upload",
});
setBlob(newBlob);
}}
>
<input name="file" ref={inputFileRef} type="file" required />
<button type="submit">Upload</button>
</form>
{blob && (
<div>
Blob url: <a href={blob.url}>{blob.url}</a>
</div>
)}
</>
);
}
后端代码如下。
app/api/avatar/upload/route.js
import { handleUpload } from "@vercel/blob/client";
import { NextResponse } from "next/server";
export async function POST(request) {
const body = await request.json();
try {
const jsonResponse = await handleUpload({
body,
request,
onBeforeGenerateToken: async (pathname /*, clientPayload */) => {
// Generate a client token for the browser to upload the file
// ⚠️ Authenticate and authorize users before generating the token.
// Otherwise, you're allowing anonymous uploads.
return {
allowedContentTypes: ["image/jpeg", "image/png", "image/gif"],
tokenPayload: JSON.stringify({
// optional, sent to your server on upload completion
// you could pass a user id from auth, or a value from clientPayload
}),
};
},
onUploadCompleted: async ({ blob, tokenPayload }) => {
// Get notified of client upload completion
// ⚠️ This will not work on `localhost` websites,
// Use ngrok or similar to get the full upload flow
console.log("blob upload completed", blob, tokenPayload);
try {
// Run any logic after the file upload completed
// const { userId } = JSON.parse(tokenPayload);
// await db.update({ avatar: blob.url, userId });
} catch (error) {
throw new Error("Could not update user");
}
},
});
return NextResponse.json(jsonResponse);
} catch (error) {
return NextResponse.json(
{ error: error.message },
{ status: 400 } // The webhook will retry 5 times waiting for a status 200
);
}
}
补充:不过我在实际开发中发现服务端怎么都收不到上传完成的回调onUploadCompleted
,本地使用IPV6公网或是部署到公网环境都不行,不知道是不是bug,索性就不处理Blob的回调了,上传完成改为由浏览器通知服务端(这样显然存在安全风险,因为客户端可以伪造一个假的上传完成通知,但我的场景恰好没有这种风险,因此使用这种方式作为替代)。
在管理界面我们可以上传和下载文件。
Edge Config是一个注册中心,简单理解就是个服务端的配置文件,我们可以远程修改其中的内容,应用程序也会读取到热更新的配置,当然实际真正的实现要更复杂一些。
npm i @vercel/edge-config
我们引入用来读取配置的get()
函数。
import { get } from "@vercel/edge-config";
然后就可以读取配置了。
const greeting = await get("greeting");
在管理界面我们可以修改配置数据。
这里我用NextJS14写了一个简单的Demo工程作为参考,存储使用的是Vercel的组件。
参考工程:https://github.com/gacfox/vercel-clipboard
它是一个“云剪贴板”,可以发送文本或是上传小文件到服务端,用于在多个设备之间复制黏贴之类的。因为我几乎不用微信、QQ,平时从电脑传一些信息到手机都是黏贴一个文件到局域网的Samba服务上,有点麻烦,因此写了这个小东西。