分类: infra

  • 部署网站该选 Vercel 还是 Cloudflare Pages

    部署网站该选 Vercel 还是 Cloudflare Pages

    全栈开发者部署网站,首选无外乎 Vercel,Cloudflare Pages 二选一。那么,这两者间有何区别呢?结合我过去一年多的体验,简单分享一下。

    我目前两边都在付费使用,且都经历了一些团队协作。我觉得本次分享应该言之有物,不过如果跟事实有出入,欢迎各位指正。

    相似之处

    两者整体体验非常接近,都很好用。

    • GitHub 导入仓库
    • 新 commit 自动部署
    • 分支自动部署,预览新版本
    • 支持多种构建脚本
    • 免费域名(二级域名)
    • 免费永久 SSL 证书
    • CDN
    • 可观的免费额度

    Vercel 的优势

    1. 域名放在哪里都可以

    Cloudfare 最大的问题就是你非得把域名根 DNS 转过去,很蛋疼。虽然我对其他域名提供商也没什么忠诚可言,但是我就懒得换……

    经网友提醒,我又查看了一下,Cloudflare Pages 不需要停放根域名到 CF,所以这点二者一致。

    Vercel 这方面比较大度,你域名放在哪里都行,只要 CNAME 过来就能用。

    2. 集成数据服务非常方便

    Vercel 集成了很多数据服务,包括 config、KV、Storage、DB,使用起来非常方便,只需要在 GUI 上点一下,然后在本地把环境变量配置一下即可。使用的时候只需要安装指定依赖,非常好理解,和常规工作流程完全一致。

    CF 也有类似的服务,但是使用起来就麻烦很多。他们家只针对自家 Worker 做了比较简单的集成,想在自己熟悉的框架里使用会比较麻烦。

    3. 团队管理更好用

    虽然 Vercel 团队需要花钱($20/人月,相当贵),但是团队管理很好用,该有的功能都有,权限配置好,用起来也很顺畅。

    CF 则不然,我到现在都没找到控制团队权限的做法,导致我无法方便的将统计报表分享给朋友。

    4. 更好的 Serverless 支持

    Next.js 本来就是 Vercel 主力开发的,所以自然,Next.js 在 Vercel 上可以放心使用,服务器环境绝对不会成为问题。默认情况下,Vercel 会使用 Edge 引擎提供更好的性能和更低廉的价格;实在不行,也可以切换到 Serverless 模式获得更好的兼容性。

    CF 只支持 node.js 的子集(相当于 Vercel Edge Function),所以如果我们的业务代码中使用的函数库包含了一些不被支持的功能,就无法使用 CF 了。

    Cloudflare Pages 的优势

    1. 提供很好用的分析工具,包括 Web Vitals

    我其实到现在都无法难理解,这东西在 Vercel 那边居然要付费使用,还得通过代码嵌入页面。

    CF Pages 这边用起来很简单。直接打开开关,它会负责把统计代码插入页面,然后我们就能看到包含访问量、来源、技术等的数据统计。更重要的是,我们还能看到 Web Vitals 数据,而 Web Vitals 数据,能直接影响 SEO,所以这个统计就非常有帮助了。

    2. 免费额度更高

    这点 Vercel 也很值得吐槽,它们家额度设计相当诡异。比如,类似(1)的访问量统计,一个月额度 25k,啥意思呢?你的网站访问量超过每天 800,这个月可能就要交钱了……我完全无法理解他们的设计思路。

    还有 KV 也是,明明是集成的 Upstash 服务,Upstash 每天免费 10K,它们家免费 5K……其它带宽、容量等就不细说了,都不敢放开用。

    CF 具体多少我没仔细看,不过印象里要多得多得多,可能是最大方的云服务商了,基本属于 $20/月 放开用。

    3. 全球最近接入

    这点说实话我不太敢确定,没有具体测过。按照官方文档的说法,Vercel 企业版是全球的,但是免费和团队版都要手动选择边缘计算服务器的部署位置。

    CF 按照后台的说法,会自动选择离用户最近的地方执行函数功能,我们就当他是全球最近接入吧。

    4. 目前 *.pages.dev 没有被墙

    这意味着即使你没有自己的域名,可以用 Cloudflare Pages 提供服务。相对来说,*.vercel.app 已经被墙的很彻底。

    不过,如果真的是持久运营的产品,还是早点切换到自己的域名吧。

    排除的其它选项

    • VPS:太麻烦了,我已经不想再自己折腾服务器了。
    • GitHub Pages:不支持边缘计算,纯静态限制太多。
    • Supabase:Deno 限制太多。

    总结

    我个人的感受:Vercel 强在开发体验(DX);CF Pages 强在量大管饱。具体怎么选择,还请大家酌情考虑。

    如果你对出海开发、全栈开发、云服务提供商有什么想法、问题和分享,欢迎留言讨论。祝大家清明安康。

  • 使用 Turnstile 保护网站

    使用 Turnstile 保护网站

    Turnstile 是 Cloudflare 近期推出的一项免费验证码服务,可以用来验证当前用户是否是真人。与 CAPTCHA 不同,它不要求我们使用 Cloudflare 网络,所以任何网站都可以使用这项服务。对我这种 Vercel 重度用户来说,Turnstile 非常合适。

    对初创 AI 产品来说,我们既怕用户滥用我们的服务,毕竟每个 token 都要花钱(甚至要花不少钱);又怕复杂的登录注册会把用户吓跑。于是验证码就成为了比较好的选择,毕竟,只要不是自动化批量请求,自然人用户随便用用,基本上不会产生问题。

    Turnstile 使用 JS 脚本收集用户在当前网页上的交互,并通过一些深度学习的模型来判断当前用户是否是真人,这样一来就可以减少验证码出现的频率,提升用户体验。另外,免费版 Turnstile 可以不限次数地使用,对于我这种白嫖党来说,也是重大利好。

    所以,综上,我近期给公司的 API 加上了 Turnstile,以避免可能产生的滥用。

    使用 Turnstile 非常简单,这里简单说一下。

    1. 首先,我们要注册一个 Cloudflare 用户。这一步应该有手就行,我就不多说了。
    2. 接下来,创建 Turnstile 应用。
    3. 然后复制保存 Site Key 和 Secret Key。
    4. 在项目中集成 Turnstile。

    Turnstile 的工作逻辑是这样的:

    1. 我们往页面里嵌入 Turnstile JS
    2. Turnstile JS 会收集用户的各种信息,作为判断用户是否是真人的依据
    3. 当用户需要与 API 交互的时候,Turnstile JS 会生成一个 token,用户需要把 token 一起提交给 API
    4. API 拿到 token,去 Turnstile Server 检查该 token 代表的用户是否是真人
    5. 如果是,就继续执行;如果不是,就返回错误
    6. 当然,还有一些衍生逻辑,比如验证来源,token 超时失效等等

    上面这些逻辑手动实现并不复杂,如果有现成的库,使用起来就更简单。我厂的 SoulScript 使用 Nuxt3 框架,有官方推荐的模组 @nuxtjs/turnstile,所以我只要按照文档进行配置,就可以在项目当中使用 Turnstile。

    不过实际测试之后,我发现还是有点问题:

    1. 免费版(Managed)的验证码窗口不是太好看,体积也不小,插入高设计感的网站,视觉效果不理想
    2. 如果在用到验证码的时候再检查用户状态,需要花费几秒到十几秒的时间,会耽误用户干正事。

    所以我加了个开关,没发现问题就先关上,等用量上去的时候再开。


    以前我比较倾向用户体系和注册登录,因为这样可以收集更多用户信息,也比较方便我们控制用户的行为。不过现在,流量越来越贵、用户越来越难搞,同时,免费服务、Serverless 越来越多,也许,local storage + 真人验证才是下一阶段我应该首选的策略。

  • 上传文件到 CloudFlare R2

    上传文件到 CloudFlare R2

    上周日突然感染流感,发烧一天多,头昏脑涨两三天,只好请假休养。不知道是不是二阳,懒得测。工作进展不多,周末适当加加班,博客适当划划水,这周就记录一下 CloudFlare R2 上传文件吧,实测效果很好,简单省事好用,推荐大家使用。

    CloudFlare R2 兼容 AWS S3 API,但是不需要那么复杂的 IAM 体系,而且直接对接 CloudFlare CDN,我觉得更好用。跟国内的众多云存储比起来,它不需要备案,还提供域名和证书,用起来更简单。所以,如果你的网站或者服务需要一个云存储,但是暂时不想负担太高的成本,我建议先试试 CF R2。

    我假设读者已经拥有 CF 账号,在 R2 里创建了 bucket,并且完成了需要的配置(好像只需要配置一个域名)。接下来,我们需要在自己的业务代码里实现上传。

    首先,我们要安装 AWS SDK。

    pnpm i @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

    接下来,我们需要在项目中建立 S3 配置,其中关键信息都写在 .env 里。

    import { S3Client } from '@aws-sdk/client-s3';
    
    const S3 = new S3Client({
      region: 'auto',
      endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
      credentials: {
        accessKeyId: process.env.R2_ACCESS_KEY,
        secretAccessKey: process.env.R2_SECRET_KEY,
      },
    });
    
    export { S3 };

    然后就可以生成预签字的上传路径,相当于提前校验用户身份,这一步需要放在服务器端进行,避免泄漏关键信息。我这里因为业务需求比较简单,没有做复杂的鉴权和检查,如果有需要,就多验证几步。

    import { PutObjectCommand } from '@aws-sdk/client-s3';
    import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
    import slugify from 'slugify';
    import { H3Event } from 'h3';
    import { S3 } from '~/lib/s3';
    import { ApiResponse, PreSignedUrl } from '~/types';
    
    export default defineEventHandler(async function (event: H3Event): Promise<ApiResponse<PreSignedUrl>> {
      // 这一步提交的是预处理信息,没有文件本身,所以还是 json
      const json = await readBody(event) as { fileName: string | undefined, fileType: string | undefined };
      const { fileName, fileType } = json;
    
      // 生成存储对象 key,其实就是文件名
      const objectKey = `${slugify(Date.now().toString())}-${slugify(fileName)}`;
    
      // 生成预签名 url
      const preSignedUrl = await getSignedUrl(S3, new PutObjectCommand({
        Bucket: process.env.PUBLIC_S3_BUCKET_NAME,
        Key: objectKey,
        ContentType: fileType,
        ACL: 'public-read',
      }), {
        // 此 URL 5分钟内有效
        expiresIn: 60 * 5, // 5 minutes
      });
    
      return {
        code: 0,
        data: {
          preSignedUrl,
          objectKey,
        },
      };
    });

    最后,我们就可以上传文件了。

      const response = await $fetch<ApiResponse<PreSignedUrl>>('/api/get-upload-url', {
        method: 'POST',
        body: {
          fileName: file.name,
          fileType: file.type,
        },
      });  
      const { preSignedUrl, objectKey } = response.data as PreSignedUrl;
      const uploadToR2Response = await fetch(preSignedUrl, {
        method: 'PUT',
        headers: {
          'Content-Type': file.type,
        },
        body: file,
      });
      if (!uploadToR2Response.ok) {
        console.error('Failed to upload file to R2');
      }

    整个上传过程比较简单,服务器只需要做一些权限校验,剩下来的过程都可以交给 R2。关键是,这样一来,因为不涉及文件操作,就可以放心交给 Serverless 来做,Vercel 友好。上传后的文件自动进入 CF CDN,也很容易使用。

    学会使用 R2 之后没多久,我的 Vercel Blob 存储的申请也通过了。必须得说,这些互联网基础设施对我们全栈开发者来说非常重要,而无障碍的域名对目前在国内还是一种奢望,所以用国外服务就成为一种必然。希望未来会更好吧。

  • 使用 CloudFlare 突破图片防盗链

    使用 CloudFlare 突破图片防盗链

    好久没更了,最近遇到很多挑战,比如使用 Flutter 开发 App。打个岔,我发现 Cursor.sh 真是个好东西,面对 Flutter 这种我完全不熟悉的语言,Cursor 提供了很方便的 AI 辅助开发,包括一系列很好用的功能:

    1. Prompt => 代码
    2. 问题解释
    3. 结合上下生成代码或提供建议

    如果是以前,我需要先看文档,学习 Get Started,然后慢慢一行一行摸索。现在我只要告诉 ChatGPT 我需要什么样的代码,它就能自动帮我升成一堆能用的代码,虽然不够好,但是比我自己慢慢搞效率高多了。

    推荐各位使用。在面对自己不熟悉的语言、框架时,真的能大幅度提升效率。

    回到正题。

    前几天遇到一个问题。有个朋友要抓某个网站上的数据做电商网站,其中有很多图片。因为网站还没有上线,也不知道能不能挣到钱,所以想先降本,尽量不要先把钱花出去。

    所以我就想,先不把图片都抓下来放自己的服务器上,直接使用对方的地址(盗链)行不行?测试一下,果然不行,有防盗链。然后我就想到 Serverless,感觉这是个很适合的场景:

    1. 先把源图片抓下来
    2. 进行一些处理,比如压缩、切割
    3. 然后返回一个新的图片

    对于我们来说,只是不需要第二步而已,那就这么做吧。接下来就是选平台,其实我一般会优先用 Vercel,毕竟现在我大部分项目都在上面跑着。不过考虑到平时推荐 CloudFlare worder 的人也很多,他们家的免费方案也很慷慨,所以我想这次试试 CF 吧。

    没想到 CF 的文档非常全,直接就提供了类似的用例教程:Custom Domain with Images · Cloudflare Workers docs,于是我就照猫画虎,搞定了这个需求。代码很简单,我觉得都不需要详细解释:

    export default {
      async fetch(request) {
        // 解析图片地址,取出 pathname 部分
        const { pathname } = new URL(request.url);
    
        // 从原始地址获取图片,直接返回即可
        return fetch(`https://src-domain.com/${pathname}`);
      },
    };

    接下来的缓存等功能都是齐备的,不需要每次都返回源站。至于墙的问题,自己的域名 CNAME 过去,也是可以直接使用的。

    CF 的还有很多教程和范例,建议大家抽空看看:Examples · Cloudflare Workers docs


    总结

    功能不大,开发体验很好,从查找文档到完成上线,总共可能花了一个小时多点。有流量之前都不用考虑费用问题。省心省力。以后准备多多用起来。

  • 聊聊 Serverless 与 Edge Function

    聊聊 Serverless 与 Edge Function

    熟悉我的同学可能记得,前几年我经常推荐 Serverless,而且我的博客里现在还挂着 LeanCloud 的推荐链接。我觉得 Serverless 对前端、全栈开发者来说简直是个福音,很多原本很难的开发工作,有了 Serverless 之后,都能很容易的由前端处理,大大扩展了前端的能力覆盖范围。

    时至今日,我们不仅有 Serverless,还有 Edge Function,后者几乎没有冷启动的问题,更能提供 SSR 功能,好上加好。很多数据存储服务也针对性地提供好用的接口。今天的开发环境对前端来说,简直无与伦比。所以我想聊聊这个话题,希望每个前端都能在 Serverless/Edge Function 得帮助下成长为全栈开发。

    Serverless 是什么?

    我就不去抄准确定义了,按照我的体验来分享:

    1. 不需要维护独立的服务器
    2. 分为两个部分:
      1. 基于行级权限控制的数据库
      2. 可以自动扩容、降容的云函数执行器
    3. 云数据库,配合行级权限,配合合理的数据逻辑,可以在前端完成大部分数据处理的工作
    4. 云函数可以自动扩容、降容,可以休眠、启动,可以完成云数据库无法放在前端的操作
    5. 云函数以函数为一个执行单位,实现更精准的资源控制

    总之,前端有了 Serverless,就可以在没有后端、没有运维的配合下,独立完成完整应用的开发,并且具备相当强的扩展能力,费用也很低。

    其它应用场景

    除此之外,Serverless 还可以用在各种有突发计算高峰的场景里,比如音视频处理。我们可以利用 Serverless 在没人使用时自动休眠(不计费),有人访问时自动启动的特性,有需求的时候才启动,完成计算后自动关机,既节省成本,又提供很高的理论并发。

    Serverless 的问题

    早期的 Serverless 调度系统有些问题。出于学习成本的考虑,早期 Serverless 多数是基于容器技术,虽然可以完整支持大部分编程语言及其 API,但是冷启动一般都需要不少时间,少则十几秒,多则几十秒。

    这么久的启动时间,对于计算密集型领域来说,问题并不严重。因为在这些场景里,几十秒的启动时间占比不高,通常来说用户需要等待的时间远比它久,自然有队列和轮询放在前面。但是对于一般的网站应用,服务普通客户,如果启动一次要几十秒,那就很难接受了。

    比如最近大热的 ChatGPT,有些同行尝试用 Serverless 做转发,就受困于冷启动,经常超时报错。

    一代更比一代强:Edge Function

    我不太确定 Edge Function 是什么时候诞生的,最初我以为 Edge Function 无非就是跑在运算力富裕的边缘节点上,本质上跟 Serverless 没什么区别,直到前些日子开始大规模使用 Vercel,深入了解之后,才明白 Edge Function 的优势。

    简单来说,无论是 Vercel、Supabase、还是 Cloudflare Worker,都抛弃了以前基于 Docker 的做法,或者精简运行时,或者使用 deno 这样更快更小的核心,将冷启动的速度降到毫秒级——基本就是网络传输的延迟级别。

    于是 Edge Function 的可用性就大大提升,可以用在各种速度敏感型的场合,比如现代框架的 SSR、代理服务器、HTML 内容改写,等等。

    目前,我的 Edge Function 主要用在以下几个场景:

    1. Nuxt.js/Next.js 的 SSR
    2. 代理 OpenAI 的 API 访问
    3. 操作 Upstash Redis 存储和修改数据
    4. 响应 Stripe 的付款请求

    Vercel Edge Function 的限制

    我用 Vercel 比较多,这里介绍一下 Vercel Edge Function 的限制。

    首先,Vercel Edge Function 是 node.js 的子集。除了拥有完全一致的语法和基本对象之外,Edge Function 并不能使用全部的系统 API,比如 fs、http/https 等。所以类似 axios 这样知名的 ajax 封装就不能使用;也不能使用 OpenAI 的官方 npm 包。

    Edge Function 只支持 ESM,所以依赖里面的包即使没有用到系统 API,也必须使用 ESM 才能使用。所以我们必须经常性的处理一下公共包。

    虽然支持 TypeScript,但部署的时候,会使用 ESM 编译,所以 import 必须使用相对路径,否则会报错。报错的内容是使用了不支持的格式,非常有误导性。

    其它就是一些系统性要求,比如代码限制、内存限制、请求体限制等,我目前都还没有撞到。大家小心点就是。比较可能遇到的问题是,Edge Function 30s 内必须开始返回内容,这在 OpenAI 开发中很容易遇到问题,开启 stream: true 就好。

    更好的生态环境

    Vercel、CloudFlare 均提供非常完整的网络环境,包括网站、CDN 缓存层等必备服务。

    如果要使用数据库,选项非常丰富,除了 Vercel 自带的各种不解渴的“小样”,我们还可以使用 Upstash Redis,MongoDB,PlantScale MySQL,Supabase PosgreSQL,以及 TiDB Cloud Serverless。

    基本上,应有尽有,几乎所有 Web 开发所需要的存储方案都能找到有免费额度的试用装。开发环境真是盛世空前。

    我们应该怎么做

    现在经济大环境不好,很多同学面临失业、降薪,V2EX 上经常有同学讨论是开网约车好还是送外卖好……这个我也没办法。但是从另一个角度来说,我们的开发环境却是史无前例的好,部署、运维成本非常低,几乎所有必须的服务都有免费试用,每家都提供清晰的文档、易用的 SDK。

    我建议,大家好好利用闲暇时间,利用现在各种生成式模型大爆发,但是市场还没稳定下来的时间节点,多多尝试,即使做不出惊世之作,也能为自己的将来累积足够多的资本,坚持到云开日出那一天。

    广告时间:我的全栈开发课程

    发现忘记打广告了,赶紧补上。

    我目前正在连载一套云原生全栈开发课程,使用 Nuxt3 + Vercel + Serverless 数据库开发。目前发布了三集,本周计划制作第四集(如果有时间就做第五集,嗯嗯),请大家观看,有号的话还请帮我一键三连,多谢。

    B站地址:Nuxt3+Vercel+Serverless 数据库全栈开发

    油管地址:Nuxt3+Vercel+Serverless数据库全栈开发 – YouTube

    总结

    曾经面试金山的时候,有位面试官问我:你这么大年纪,你觉得你比年轻人有什么优势?我答道:因为我年纪足够大,见识过足够多的技术发展,所以我能更快地学会一门技术,也能更准确的判断一门技术是否值得学习。我一直很看好 Serverless,如今有了 Edge Function,serverless 更好用;有了各种服务,serverless 更强大。Serverless 是未来全栈不可或缺的技术。

    希望大家都学会使用。有任何问题、建议、意见,欢迎留言讨论。