Vercel应用部署指南
Vercel是一个能免费托管Web站点的云平台,相比于Github Pages,Vercel的功能要更强大得多,Vercel上不仅可以部署静态站点,它还提供了CI/CD、多环境、Edge Functions,最近还新增了存储服务,也可以免费使用。相比于完整的Linux主机虽然还有诸多限制,但部署一些轻量级的应用程序是完全够用的。
此外Vercel团队对开源社区的贡献也很多,NextJS和TurboPack都是Vercel的知名开源项目。
官方文档:https://vercel.com/docs
部署静态站点参考文档: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.localEdge 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实现前端,服务端仍然部署在我们自己的服务器上。
使用Storage存储参考文档: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),开发阶段调试时千万别传太大的数据,否则瞬间用光免费额度就没法继续开发了。
Vercel KVKV的用法非常简单,和大多数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服务上,有点麻烦,因此写了这个小东西。