在构建个人摄影博客时,我们往往面临一个两难的选择:
最近,我为博客的相册功能探索出了一套"混合式"解决方案:前端展示使用经过 Sharp 极致压缩的本地预览图,而当用户需要收藏时,则通过 Cloudflare R2 提供原图下载。
Next.js 的 next/image 组件配合 sharp 库,是提升首屏体验的神器。
我们在项目中引入 sharp:
bun add sharp通过保留图片在项目本地(public 目录),Next.js 可以在构建时或运行时自动分析图片的主色调、尺寸,并生成体积极小的 WebP/AVIF 格式缩略图。
这意味着,当用户浏览相册列表时,加载的并非几 MB 的原片,而是经过裁剪和优化的、仅有几十 KB 的"视觉替身"。这保证了页面滑动的丝般顺滑。
虽然本地预览很快,但用户(尤其是摄影爱好者)往往希望下载未被压缩的原始照片(Raw/High-Res JPG)。如果让 Next.js 服务器直接承载这些大文件的下载流量,不仅占用带宽,还可能影响网站的响应速度。
因此,我引入了 Cloudflare R2 作为"原片冷存储"。
R2 的优势在于零出口费(Zero Egress Fees),非常适合用作大文件的下载源。我们将所有高清原图上传至 R2,并配置好自定义域名 image.superstream.space。
在前端开发中,我们实现了一个智能的 Lightbox(灯箱)。
/images/albums/...,由 Next.js 实时优化渲染。为了防止浏览器直接打开图片(而不是下载),由于 R2 存在跨域限制(CORS),我们采用了 fetch + Blob 的技术方案:
const handleDownload = async (e: React.MouseEvent) => {
// 1. 动态构建 R2 原图地址
const imageUrl = images[selectedIndex].startsWith('/')
? `https://image.superstream.space${images[selectedIndex]}`
: images[selectedIndex]
try {
// 2. 绕过浏览器默认行为,以二进制流形式获取图片
const response = await fetch(imageUrl)
const blob = await response.blob()
// 3. 创建临时链接触发下载
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = imageUrl.split('/').pop() || 'photo.jpg' // 自动提取文件名
document.body.appendChild(link)
link.click()
// 4. 释放内存
window.URL.revokeObjectURL(url)
} catch (error) {
console.error('Download failed:', error)
}
}这套方案的精髓在于“各司其职”:
这种架构不仅大幅提升了用户体验,也有效地降低了服务器的运维压力,是摄影类博客的一个理想参考范本。