← 返回蜂巢洞察

如果你的网站即将达到 Vercel 提供的免费图像优化服务的上限,你应该怎么办?

我维护着自己的网站,偶尔会将其升级到新版本。在升级的过程中,我也会经常尝试不同的技术和架构。 最近,我遇到了一个问题:Vercel向我发出了警告,提示我在“图像优化”功能上即将达到免费版本的使用限制。经过几次尝试和调整,我最终解决了这个问题,确保该功能不会不必要的超出我的免费版本使用范围。 在这篇文章中,我将解释具体出现了哪些问题,我是如何找出问题的根源的,以及我采取了哪些措施来解决问题。同时,我也会向大家详细介绍我所选择的方法以及是如何成功解决这个问题的。 那么,问题到底是什么呢? 我现在使用的网站版本是第5版(发布于2026年),该版本使用了Cloudflare R2对象存储提供的CDN服

我维护着自己的网站,偶尔会将其升级到新版本。在升级的过程中,我也会经常尝试不同的技术和架构。

最近,我遇到了一个问题:Vercel向我发出了警告,提示我在“图像优化”功能上即将达到免费版本的使用限制。经过几次尝试和调整,我最终解决了这个问题,确保该功能不会不必要的超出我的免费版本使用范围。

在这篇文章中,我将解释具体出现了哪些问题,我是如何找出问题的根源的,以及我采取了哪些措施来解决问题。同时,我也会向大家详细介绍我所选择的方法以及是如何成功解决这个问题的。

那么,问题到底是什么呢?

我现在使用的网站版本是第5版(发布于2026年),该版本使用了Cloudflare R2对象存储提供的CDN服务。此外,我的网站还针对网页搜索和人工智能搜索进行了大量的SEO优化。

你们可以直接访问我的网站:fahimbinamin.com。虽然不确定在访问网站时能看到哪个版本,但网站上显示的始终是最新的版本。

Fahim的网站(第5版)

由于我的网站第5版是一个多页面结构,我试图将所有与我自己相关的内容都整合到一个平台上。我在很多地方发表过文章,因此我希望能够将这些文章全部放在同一个平台上展示。同时,我也需要确保这些文章的 canonical URL能够正常使用,这样人工智能爬虫或机器人才能正确识别文章的原始发布地址。因此,我在网站第5版中加入了这一功能。

我的网站使用了Next.js以及Next提供的图像优化技术。目前,该网站的代码库是封闭源码的,大家无法直接访问它。不过,我已经分享了一张截图,让大家能够了解当前网站的具体架构。

Fahim的网站(第5版)的代码库在GitHub上(封闭源码)

无论如何,为了节省成本,我总是会尽可能有效地利用免费资源。对于CDN服务,我选择了Cloudflare与R2对象存储的组合;我网站上所有的媒体文件(图片、PDF等)都来自Cloudflare R2对象存储,而这些文件是通过Vercel来提供的。

直到昨天之前,一切都很顺利。然而那天,我收到了Vercel发来的邮件,提示我在图像优化功能上即将达到免费版本的使用限制。

关于图像优化的警告

我打开使用设置面板,本以为会看到自己项目的图片:个人资料照片、项目截图以及图库中的图片。然而,列表中几乎所有的图片来源都是freecodecamp.org

有那么几秒钟,我真的很困惑,因为我的图片并不是存储在freeCodeCamp上的。后来我才想起来,其实这些图片的链接是我自己添加的!

目录

我将这篇文章分成了多个章节,这样如果你想要或需要单独阅读某个特定部分,就可以直接跳到相应的章节。

这些图片的实际来源

这些年来,我把很多自己写的文章都发布到了freeCodeCamp上。当我把这些文章重新导入到这个网站的博客存档中时,我保留了文章中的所有内容,包括那些指向freeCodeCamp和Hashnode内容分发网络的标签(因为这些地方本来就是截图的存储位置)。

博客渲染器会将所有的内嵌图片包裹在BlogImage组件中:

import Image from "next/image";
 
export default function BlogImage({ src, alt, caption }) {
  return (
    
{alt}{caption}</figcaption>
); }

next/image其实并不关心src指向的是你的域名还是别人的域名。只要托管地址被列在next.config.ts文件中的images.remotePatterns里,Next.js就会在处理这些图片时通过Vercel的图像优化流程,包括获取原始图片、根据不同屏幕尺寸重新调整图片大小、将其重新编码为AVIF/WebP格式,然后缓存处理结果。

几个月前,我分别在两次提交中把cdn-media-0.freecodecamp.orgwww.freecodecamp.orgcdn.hashnode.com添加到了允许列表中。我这样做是为了确保那些重新导入的图片能够正常显示,而不会出现任何问题。

在这个案例中,我的方法确实奏效了。但同时也意味着,所有这些图片(以及Next.js根据它们生成的各种不同尺寸的图片)都会占用我Vercel账户中的免费图像优化额度。我不想随意消耗这个额度,因为这个额度是每月自动重置的,而且我也无法控制爬虫或页面加载会多次触发同一图片的不同尺寸版本。

确认处理范围

在采取任何行动之前,我需要确切的数据而不是猜测。我在content/posts/*.mdx文件中查找所有包含src="..."属性的代码段,这些属性指向三个外部服务器:

src="(https://(?:cdn-media-0\.freecodecamp\.org|www\.freecodecamp\.org|cdn\.hashnode\.com)[^"]*)"

最终在33篇帖子中发现了691个引用外部图片的代码段:这些截图来自2021年之前的教程,内容涵盖了磁盘分区操作指南、CUDA配置说明等等。所有这些资源其实都是在使用他人的CDN进行优化处理的,而不是被缓存后直接提供给用户。这就是之前导致问题的原因。

在寻找解决方案的过程中,我找到了两种能够迅速解决这个问题的方法。

两种解决问题的方法

方案1:停止对这些资源进行优化处理。对于那些不是来自我的CDN的资源,将unoptimized参数传递给next/image函数,这样Vercel就不会再处理这些请求了。

采用这种方案后,浏览器会直接从freeCodeCamp获取图片资源,不会出现图像尺寸调整或AVIF格式转换的情况,也不会影响网站的带宽使用量。因此,这个修改只需几分钟就能完成。

方案2:将这些图片资源托管到自己的服务器上。下载这691张图片,将它们重新存储在我已经用于网站其他图片的Cloudflare R2存储桶中,然后修改src属性,使其指向cdn.fahimbinamin.com,同时完全删除代码中提到的三个外部服务器地址。

我选择了哪种方案

方案1需要的修改工作量很小,看起来很吸引人。但是这意味着我的博客中三分之一的内容将无法使用响应式布局和现代图像格式,而且整个网站的运行还依赖于freeCodeCamp的CDN服务,这些外部服务的URL也不能更改。

方案2虽然前期需要投入更多的精力,但它与网站上其他图片资源的处理方式是一致的。此外,采用这种方案后仍然可以享受优化带来的好处,而且完全摆脱了对外部服务的依赖。因此,我最终选择了方案2。

进行迁移操作

步骤1:下载所有文件

我编写了一个简单的Node.js脚本,该脚本会遍历所有的帖子,查找那些包含上述三个外部服务器地址的代码段,并下载相应的图片资源:

const HOST_RE =
  /src="(https:\/\/(?:cdn-media-0\.freecodecamp\.org|www\.freecodecamp\.org|cdn\.hashnode\.com)[^"]*)"/g;
 
// ...对于每一个匹配到的结果,将其下载到downloaded-images/<slug>>/<>filename>>目录中,
// 并将原来的URL地址保存到url-map.json文件中

为了避免文件名称重复,这些文件是按照帖子的唯一标识符进行分类存储的。因为很多截图的文件名都是类似的,比如2022-01-20_18-50.png,而这些截图可能出现在完全不同的教程中。最终,总共下载了691个文件,没有出现任何错误,总大小约为105MB。

步骤2:将文件上传到R2

我重新整理了下载文件夹的结构,使其与目标路径格式保持一致(images/writing//...),然后使用CS Browser将这些文件上传到了存储桶中。R2提供了与S3兼容的API,而图形化客户端在处理大量嵌套文件时,其效率要比通过控制面板拖动文件夹高得多。

步骤3:在修改内容之前进行验证

在开始修改任何内容之前,我先抽查了几个新上传的文件的链接,进行了测试:

curl -s -o /dev/null -w "%{http_code}\n" \
  "https://cdn.fahimbinamin.com/images/writing/automount-a-storage-partition-on-startup-in-linux/HDD-Partition.png"
# 返回结果:200

步骤4:修改MDX文件内容

另一个脚本会读取url-map.json文件,然后将content/posts/*.mdx中所有匹配的src="..."替换为cdn.fahimbinamin.com这个URL地址:

content = content.replace(HOST_RE, (match, url) => {
  const entry = urlMap[url];
  if (!entry) return match; // 如果没有对应的条目,就保持原样
  return `src="${CDN_BASE}/${entry.cdnPath}"`;
});

测试结束后,我发现共有33个文件的内容发生了变化,有692处进行了替换(其中有一张图片在同一篇文章中被引用了两次),而没有任何内容没有被正确处理。

步骤5:清理配置文件

由于所有标签引用的都是外部服务器,所以我从next.config.ts文件中的images.remotePatterns列表中删除了这三项配置:

// 这些配置已经不再需要,因此被删除了
{ protocol: "https", hostname: "cdn.hashnode.com", pathname: "/**" },
{ protocol: "https", hostname: "cdn-media-0.freecodecamp.org", pathname: "/**" },
{ protocol: "https", hostname: "www.freecodecamp.org", pathname: "/**" },

之后我进行了完整的生产环境构建测试。所有43篇以上的博客文章在预渲染后都没有问题,代码检查也通过了,没有任何功能出现故障。

为什么从长远来看,这是更好的解决方案

实际上,配额限制的问题只是另一个更根本的错误的表现——我把第三方CDN服务当作自己能够完全控制的基础设施来使用。在很长一段时间里,这种做法都没有问题,直到使用量超过了我原本没有密切关注的阈值。

如果只是简单地移除unoptimized这个配置选项,虽然警告信息会消失,但根本无法解决根本问题。而且,我仍然要依赖freeCodeCamp提供的URL地址,而这些地址是否有效并没有任何保障;同时,也不会有任何优化效果出现。

通过自行托管资源,现在这个网站上的所有图片都经过了相同的处理流程,都会使用缓存机制和CDN服务,无论这些内容最初存储在什么地方。我绝对不能允许因为别人的URL结构发生了变化,而导致我们的系统出现故障。

下次我会采取不同的做法

对于freeCodeCamp和Hashnode这两个平台,相关的remotePatterns配置项分别是相隔数月才添加的。每次添加这些配置都是因为一些被补发的帖子中的图片链接出现了问题,而将这些图片重新托管到合适的服务器上是最快捷的解决办法。

在之前,我从未问过自己这样一个问题:“这些内容真的应该托管在这里吗?还是从一开始就应该放在我自己的内容分发网络上呢?”在开始补发内容之前问这个问题其实很简单,但等到已经有691张图片和33篇帖子被发布出来,并且这些内容的链接都指向了外部服务器之后,再来回答这个问题就变得非常麻烦了。

现在我已经把这条规则记录了下来,因此今后在补发任何内容时,都会先下载这些资源并将其重新托管到合适的服务器上,确保没有任何标签的链接会指向外部网站。

结论

非常感谢您耐心读完了这篇文章。希望如果将来您遇到类似的问题,这篇文章能对您有所帮助。

如果您想了解更多这样的内容,可以关注我在LinkedInX上的动态。如果您对开源技术和开发感兴趣,还可以访问我的网站GitHub上的账号。如果您喜欢观看与编程和技术相关的视频,也可以订阅我的YouTube频道

相关文章

技术实践

了解人工智能软件开发生命周期流程——构建智能代理功能的完整指南

也许你可以理解这样的场景:本周,你用了同样的说明四次向别人解释人工智能模型的使用方法。 你反复讲解过团队是如何构建演示文稿的框架的,哪些检查步骤需要在部署之前完成,以及为什么测试数据库并不是文档中提到的那个。 每次你都要把这些内容重新写一遍,每次智能助手也能完成得不错,但每次新的会话开始时,一切都得从零开始。 而这正是 智能助手技能 所要解决的问题。 技能 实际上就是一个文件夹,其中只包含一个名为 Skill.md 的文件。智能助手在启动时会阅读其中的一行总结内容,而只有当真正需要时才会打开完整的说明文件。你只需把解释内容编写一次,将其与代码一起提交,那么团队中的每个智能助手就能访问这些信息,

阅读全文
技术实践

如何管理代码库中的上下文文件,从而让人工智能编码助手产生更优质的成果

你向编码助手请求创建一个新的端点,90秒后,这个新的端点就已经可以正常使用了。 然后你查看代码变更内容,发现它引入了一个并不存在于你的`package.json`文件中的验证库;尽管你们的团队在去年春天就已经改用了Node.js的测试框架,但它仍然使用Jest来编写测试用例;此外,由于它不知道代码库中其他处理函数都是通过服务来调用的,所以它直接从路由处理函数内部访问了数据库。 代码可以运行,它编写的测试用例也能通过,但你们还是不得不重写大部分代码。 这些情况都不是模型本身的问题——它确实给出了一个看似合理的解决方案,但它之所以会犯这些错误,是因为没有人告诉它这个特定的代码库是如何运作的。 你们

阅读全文
技术实践

如何利用Gemini构建人工智能功能:面向开发者的提示工程实用指南

大多数关于提示工程的教学教程都遵循相同的流程:安装SDK,输入API密钥,调用 generateContent 函数,然后打印输出结果。模型会生成一些看似合理的内容,之后教学教程也就结束了。 但当你真正尝试将这个系统投入实际使用时,才会发现其实真正的准备工作根本还没有开始。 “API返回的文本”与“让用户感到可信的实际功能”之间的差距,正是需要耗费大量精力去解决的地方。 这个差距中充满了各种棘手的问题:模型生成的内容听起来和其他聊天机器人没什么两样;它会编造用户从未说过的话;它返回的数据会被用Markdown格式包裹起来;系统会在凌晨2点出现故障;而对于那些只是想得到答案的用户来说,系统展示的

阅读全文
技术实践

如何使用Python构建一个人工智能文件分析工具

如果你曾经打开过一份30页的PDF文件,然后心想“我绝对不可能读完这一切”,那么你就已经理解了为什么文件分析人工智能工具会非常有用。 想象一下,当你上传一篇研究论文、简历、CSV文件、商业报告或PDF文档后,只需简单地问这样一个问题: “其中最重要的发现是什么?” 人工智能工具无需你手动浏览整个文件,就能理解文件的内容,并回答相关问题。 在本教程中,我们正是要构建这样的工具。我们将使用Python编写一个适合初学者的 AI文件分析工具 ,它能够: 从你的电脑中接收文件 将文件上传到人工智能模型中 读取文件的内容 理解自然语言提出的问题 分析文件 给出有用的答案 处理各种类型的问题,而无需我们为

阅读全文