博客接入 R2 以后,事情并没有自动结束
记录这个网站如何使用对象存储管理图片和音频,以及公开地址、缓存和图片优化仍需处理的细节。
网站里的照片和音乐比代码更占空间,也不适合随着每次构建一起塞进应用镜像。这个项目现在使用 Cloudflare R2 存放上传的媒体,Next.js 负责签名上传、记录资源信息和在页面里引用公开地址。
把文件传进 Bucket 只是第一步。对象能不能稳定访问、浏览器会缓存多久、页面请求的是不是合适尺寸,都需要继续处理。
上传链路放在服务端
R2 提供 S3 兼容接口,所以项目使用 AWS SDK 创建客户端。账户 ID、访问密钥、Bucket 名称和 endpoint 都来自服务端环境变量,不能出现在浏览器包中。
后台上传时可以让服务器直接接收文件,也可以由服务器签发短期上传地址。无论哪种方式,浏览器都不应该拿到永久密钥。接口还需要限制文件类型和体积,并为对象生成可控的 key,避免用户提交的文件名直接变成存储路径。
const client = new S3Client({
region: "auto",
endpoint: process.env.R2_ENDPOINT,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
});这段代码只说明客户端怎样连接,真正的上传接口还要做身份验证和输入校验。
公开地址和管理接口分开
S3 endpoint 用来管理对象,访客看到的图片地址则来自 R2_PUBLIC_URL。把两者混在一起,常见结果是上传成功却拿到一个不能公开访问的地址。
生产环境更适合绑定自己的媒体域名,并为跨域、内容类型和缓存策略设置清晰规则。仓库里允许 Next.js Image 组件读取配置好的 R2 域名,但“允许加载”不等于“已经优化”。如果组件绕过图片优化,原图仍可能以很大的体积传给手机端。
images: {
remotePatterns: [
{
protocol: "https",
hostname: "你的媒体域名",
pathname: "/**",
},
],
}缓存要看真实响应头
我以前很容易在文章里写“图片缓存 30 天”,仿佛配置过就一定生效。更可靠的办法是直接检查公开文件的响应头:
curl -I "https://你的媒体域名/path/to/image.jpg"如果没有合适的 Cache-Control,浏览器和边缘节点未必会按预期复用资源。文件名一旦使用长期缓存,就最好带内容哈希或版本,更新图片时换一个 key,避免旧内容一直留在缓存里。
音频还要单独考虑。播放器首屏只需要元数据,不应该一打开网站就下载整首歌;真正播放时再加载,能减少与正文图片争抢带宽。
我现在怎样判断它有没有价值
我不再用一个没有测量依据的“节省百分之多少”下结论。更值得看的指标是:应用服务器还承担多少媒体流量、首页传输体积是否下降、同一张图片二次访问有没有命中缓存、移动端是否只拿到所需尺寸。
R2 解决了媒体文件放在哪里的问题,却不会自动解决交付效率。上传权限、公开域名、缓存、图片尺寸和备份一起做好,它才真正成为网站的一部分。