📷 EXIF Photo Blog(中文安装与使用指南)
本项目修改自 sambecker/exif-photo-blog,感谢原作者的杰出工作。
项目地址:https://github.com/10000ge10000/photo-blog
✨ 功能特性
- 内置登录认证
- 照片上传与 EXIF 元数据提取(相机/镜头/曝光等)
- 标签组织与浏览
- 无限滚动
- 明/暗色主题
- 自动生成 Open Graph(OG)社交分享图
- 内置 CMD-K 全局命令面板与照片搜索
- AI 生成标题/说明/语义摘要/标签(可选)
- RSS 与 JSON Feeds
- Fujifilm 胶片模拟与配方支持
🛠️ 安装部署
欢迎来到部署环节!推荐使用 Vercel 一键部署,整个过程就像泡一杯咖啡一样简单 ☕。Vercel 会自动帮您搞定数据库和图片存储。
🚀 步骤 1: 一键部署
或者 Fork 本仓库 后点击 https://vercel.com/new

-
下载环境变量 .env 下载地址:.env
-
在 Import.env 中手动导入下载的 .env

-
填写必要的环境变量

- 环境变量:
ADMIN_EMAIL - 说明: 设置用于登录后台
/admin的管理员邮箱和密码。 - 值:
[email protected](请替换为您的真实邮箱) - 环境变量:
ADMIN_PASSWORD - 值:
一个强密码(请设置一个足够安全的密码) - 环境变量:
AUTH_SECRET - 值: 请访问 https://generate-secret.vercel.app/32 生成一个 32 位的随机字符串,并填入环境变量。
- 环境变量:
NEXT_PUBLIC_DOMAIN - 说明: 设置您的站点域名,它将用于生成分享链接,并且在未设置导航标题时显示在导航栏。
- 值:
photo.example.com
- 环境变量:
⚙️ 步骤 2: 手动配置
为了让您的站点正常运行,还需要配置一些环境变量。
📦 数据库与存储 (Database & Storage)
| 存储方案 | 免费额度 | 部署难度 | 部署成本 |
|---|---|---|---|
| Vercel Blob | 每月1 GB 存储容量、10 GB 数据传输/出站流量 | ⭐ | 💰(零星) |
| Cloudflare R2 | 每月10 GB 存储空间、1,000 万次 A 类操作 | ⭐⭐⭐ | 💰(零星) |
| AWS S3 | 首年5 GB 标准存储、20,000 次 Get 请求、2,000 次 Put 请求 | ⭐⭐⭐ | 💰(零星) |
| 自建 MinIO | 成本取决于自有服务器与带宽资源 | ⭐⭐⭐⭐✨(4.5 星) | 💰💰💰💰💰 |
🗄️ 数据库 (Database)
- Vercel Postgres: Vercel 提供的免费 Postgres 数据库。
- 使用方法: 在 Vercel 项目的 "Storage" 选项卡中,选择 "Postgres" 并点击 "Create Database"。创建后,将其连接到您的项目即可。Vercel 会自动为您设置所有必需的环境变量。
- 免费额度: 每月 512MB 存储空间、1GB 内存和 10 万次数据库执行。
🖼️ 对象存储 (Storage)
您需要选择并配置以下其中一种对象存储方案:
- Vercel Blob: Vercel 自家的对象存储,适合存放照片等文件。
- 使用方法: 在 Vercel 项目的 "Storage" 选项卡中,选择 "Blob" 并点击 "Create Store"。创建后,连接到您的项目即可。
- 免费额度: 每月 1GB 存储容量和 10GB 数据传输/出站流量。
- Cloudflare R2: Cloudflare 提供的 S3 兼容存储,以零出口带宽费用著称。
- 免费额度: 每月 10GB 存储空间、1000 万次 A 类操作(写/列举)和 1 亿次 B 类操作(读)。
- 配置: 详见后文「可选对象存储提供商」章节。
- AWS S3: 亚马逊提供的成熟、功能丰富的对象存储服务。
- 免费额度: 首年 5GB 标准存储、2 万次 Get 请求和 2 千次 Put 请求。
- 配置: 详见后文「可选对象存储提供商」章节。
- MinIO: 一款开源的、可自托管的 S3 兼容对象存储。
- 免费额度: 开源免费,成本为您自己的服务器和带宽费用。
- 配置: 详见后文「可选对象存储提供商」章节。
🔐 身份认证 (Authentication)
🔑 设置 Auth 密钥
- 说明:
AUTH_SECRET是一个极其重要的安全密钥,用于加密和验证用户登录会话。缺少此密钥将导致无法登录。 - 环境变量:
AUTH_SECRET - 值: 请访问 https://generate-secret.vercel.app/32 生成一个 32 位的随机字符串,并填入环境变量。
🧑💻 设置管理员用户
- 说明: 设置用于登录后台
/admin的管理员邮箱和密码。 - 环境变量:
ADMIN_EMAIL - 值:
[email protected](请替换为您的真实邮箱) - 环境变量:
ADMIN_PASSWORD - 值:
一个强密码(请设置一个足够安全的密码)
🌐 基础内容 (Content)
🗣️ 配置语言
- 说明: 设置站点前台展示的语言。
- 环境变量:
NEXT_PUBLIC_LOCALE(更多支持的语言请参考 README 后续章节) - 值:
zh-cn
🔗 配置域名
- 说明: 设置您的站点域名,它将用于生成分享链接,并且在未设置导航标题时显示在导航栏。
- 环境变量:
NEXT_PUBLIC_DOMAIN - 值:
photo.example.com
📝 配置页面标题
- 说明: 设置在浏览器标签页和搜索引擎结果中显示的标题。
- 环境变量:
NEXT_PUBLIC_META_TITLE - 值:
Photo Blog
🎉 步骤 3: 上传你的第一张照片!
恭喜!所有配置都已完成。现在,让我们来发布第一张作品吧!
- 访问
https://<您的域名>/admin - 使用您刚刚设置的管理员账号登录。
- 点击「Upload Photos」上传您的照片。
- (可选)为您的照片添加标题、说明等信息。
- 点击「Create」完成创建!
🎛️ 进阶自定义
AI 文本生成(可选)
⚠️ 重要说明:启用 AI 功能会产生第三方费用(OpenAI)。请务必设置用量上限和速率限制,避免意外开销。不要把 OpenAI 密钥设置为以 NEXT_PUBLIC_ 开头的变量。
- 开通 OpenAI 并充值(如遇权限问题参见 Issues #110)
- 生成 API Key,并在环境变量中设置:
OPENAI_SECRET_KEY - 推荐:通过 Upstash Redis 添加「速率限制」
- 在 Vercel 的 Storage 选项卡中创建 Upstash Redis,并绑定到项目
- 若要求设置前缀,建议使用
EXIF
- 可选:指定哪些字段在上传时自动生成(逗号分隔):
AI_TEXT_AUTO_GENERATED_FIELDS = title,semantic- 可选值:
alltitle(默认)captiontags(默认)semantic(默认)none
- 可选:使用兼容 OpenAI 的其他服务
- 设置
OPENAI_BASE_URL
- 设置
Vercel Analytics 与 Speed Insights(可选)
- Analytics:在 Vercel 控制台点击「Analytics」并启用(项目已包含
@vercel/analytics) - Speed Insights:在 Vercel 控制台点击「Speed Insights」并启用(项目已包含
@vercel/speed-insights)
可选配置(环境变量)
以下环境变量用于控制站点内容、性能、分类、排序、显示、网格、设计、设置等行为。
内容(Content)
NEXT_PUBLIC_META_TITLE:用于搜索结果与浏览器标签NEXT_PUBLIC_META_DESCRIPTION:用于搜索结果NEXT_PUBLIC_NAV_TITLE:右上角导航标题(未配置时默认显示域名)NEXT_PUBLIC_NAV_CAPTION:右上角副标题NEXT_PUBLIC_PAGE_ABOUT:网格页侧栏简介(支持<b> <strong> <i> <em> <u> <br>标签)NEXT_PUBLIC_DOMAIN_SHARE:分享弹窗使用的短域名
性能(Performance)
⚠️ 启用会增加平台用量。若遇到构建失败,可参考 FAQ 的排查建议。
NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTOS = 1:照片详情页p/[photoId]静态化(构建时预渲染)NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_OG_IMAGES = 1:照片 OG 图静态化NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_CATEGORIES = 1:分类页(如tag/[tag]、shot-on/[make]/[model]等)静态化NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_CATEGORY_OG_IMAGES = 1:分类页 OG 图静态化NEXT_PUBLIC_PRESERVE_ORIGINAL_UPLOADS = 1:保留原图(上传前不压缩)NEXT_PUBLIC_IMAGE_QUALITY = 1-100:大图压缩质量(默认 75)NEXT_PUBLIC_BLUR_DISABLED = 1:禁用占位模糊数据(可减少 Postgres 使用量)
分类(Categories)
NEXT_PUBLIC_CATEGORY_VISIBILITY:控制侧栏与 CMD-K 中出现哪些集合及顺序(逗号分隔)。例如将相机置于标签前,并隐藏胶片模拟:cameras,tags,lenses,recipes。- 可选值:
recents(默认)、years、tags(默认)、cameras(默认)、lenses(默认)、recipes(默认)、films(默认)、focal-lengths
- 可选值:
NEXT_PUBLIC_HIDE_CATEGORIES_ON_MOBILE = 1:移动端隐藏侧栏分类NEXT_PUBLIC_HIDE_CATEGORY_IMAGE_HOVERS = 1:悬停分类链接时不显示图片预览NEXT_PUBLIC_EXHAUSTIVE_SIDEBAR_CATEGORIES = 1:侧栏固定为展开状态NEXT_PUBLIC_HIDE_TAGS_WITH_ONE_PHOTO = 1:仅显示至少包含 2 张照片的标签
排序(Sorting)
NEXT_PUBLIC_DEFAULT_SORT:网格/满幅首页默认排序- 可选值:
taken-at(默认)、taken-at-oldest-first、uploaded-at、uploaded-at-oldest-first
- 可选值:
NEXT_PUBLIC_NAV_SORT_CONTROL:首页排序控件显示方式- 可选值:
none、toggle(默认)、menu
- 可选值:
- 颜色排序(实验特性)
NEXT_PUBLIC_SORT_BY_COLOR = 1:启用基于颜色的排序(强制使用menu控件,并在后台标记缺少颜色数据的照片)NEXT_PUBLIC_COLOR_SORT_STARTING_HUE:起始色相(0-360,默认 80)NEXT_PUBLIC_COLOR_SORT_CHROMA_CUTOFF:色度阈值(0-0.37,默认 0.05)
NEXT_PUBLIC_PRIORITY_BASED_SORTING = 1:排序时考虑优先级字段(可能影响性能)
显示(Display)
NEXT_PUBLIC_HIDE_KEYBOARD_SHORTCUT_TOOLTIPS = 1:隐藏快捷键提示NEXT_PUBLIC_HIDE_EXIF_DATA = 1:隐藏 EXIF 信息(适合以作品集为主的站点)NEXT_PUBLIC_HIDE_ZOOM_CONTROLS = 1:隐藏全屏大图缩放控件NEXT_PUBLIC_HIDE_TAKEN_AT_TIME = 1:隐藏拍摄时间(只保留日期)NEXT_PUBLIC_HIDE_REPO_LINK = 1:隐藏页脚仓库链接
网格(Grid)
NEXT_PUBLIC_GRID_HOMEPAGE = 1:首页使用网格布局NEXT_PUBLIC_GRID_ASPECT_RATIO = 1.5:网格缩略图显示比例(默认 1;设为 0 取消约束)NEXT_PUBLIC_SHOW_LARGE_THUMBNAILS = 1:强制使用较大缩略图(否则由比例决定密度)
设计(Design)
NEXT_PUBLIC_DEFAULT_THEME = light | dark:默认主题(未配置时为system)NEXT_PUBLIC_MATTE_PHOTOS = 1:为照片加“画框”边距与边框(适合竖图;颜色可通过NEXT_PUBLIC_MATTE_COLOR与NEXT_PUBLIC_MATTE_COLOR_DARK设置)
设置(Settings)
NEXT_PUBLIC_GEO_PRIVACY = 1:隐私模式,不收集/展示地理信息(会重新压缩以移除 GPS)NEXT_PUBLIC_ALLOW_PUBLIC_DOWNLOADS = 1:允许所有访客下载原图(可能增加带宽消耗)NEXT_PUBLIC_SOCIAL_NETWORKS:分享弹窗中的社交平台列表(逗号分隔)- 可选值:
x(默认)、threads、facebook、linkedin、all、none
- 可选值:
NEXT_PUBLIC_SITE_FEEDS = 1:启用/feed.json与/rss.xmlNEXT_PUBLIC_OG_TEXT_ALIGNMENT = BOTTOM:OG 文本底部对齐(默认顶部)
页面脚本与分析(Scripts & Analytics)
PAGE_SCRIPT_URLS:以逗号分隔的一组 URL,将注入至 body 底部(通过 next/script)- URL 必须以
https开头 - ⚠️ 将在每个页面执行任意脚本,请谨慎使用
- URL 必须以
🗄️ 可选对象存储提供商(单选)
仅支持同时启用一种存储:Vercel Blob、Cloudflare R2、AWS S3 或 MinIO。建议在上传任何照片之前完成配置。
- 若配置了多种,可通过
NEXT_PUBLIC_STORAGE_PREFERENCE指定优先级:aws-s3、cloudflare-r2、minio、vercel-blob
Cloudflare R2
1)创建 Bucket 并设置 CORS:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT"],
"AllowedOrigins": [
"http://localhost:3000",
"https://{VERCEL_PROJECT_NAME}*.vercel.app",
"{PRODUCTION_DOMAIN}"
]
}
]
- 启用公共访问:绑定自定义域(Cloudflare 域名)或允许使用
r2.dev公共子域 - 设置公有端变量:
NEXT_PUBLIC_CLOUDFLARE_R2_BUCKETNEXT_PUBLIC_CLOUDFLARE_R2_ACCOUNT_IDNEXT_PUBLIC_CLOUDFLARE_R2_PUBLIC_DOMAIN(自定义域或*.r2.dev)
2)创建 API Token(仅限目标 Bucket 的读写权限)并设置私有凭据(不要带 NEXT_PUBLIC 前缀):
CLOUDFLARE_R2_ACCESS_KEYCLOUDFLARE_R2_SECRET_ACCESS_KEY
AWS S3
1)创建 Bucket:开启 ACL,关闭“阻止全部公共访问”;设置 CORS:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT"],
"AllowedOrigins": [
"http://localhost:*",
"https://{VERCEL_PROJECT_NAME}*.vercel.app",
"{PRODUCTION_DOMAIN}"
],
"ExposeHeaders": []
}
]
- 设置公有端变量:
NEXT_PUBLIC_AWS_S3_BUCKETNEXT_PUBLIC_AWS_S3_REGION(例如us-east-1)
2)创建 IAM 策略 + 用户,分配读写权限并生成密钥(不要带 NEXT_PUBLIC 前缀):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:PutObjectACL",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::{BUCKET_NAME}",
"arn:aws:s3:::{BUCKET_NAME}/*"
]
}
]
}
- 设置私有凭据:
AWS_S3_ACCESS_KEY、AWS_S3_SECRET_ACCESS_KEY
MinIO(自托管,兼容 S3)
1)部署 MinIO 并创建 Bucket;为 Bucket 设置只读公共策略(示例略);
- 公有端变量:
NEXT_PUBLIC_MINIO_BUCKETNEXT_PUBLIC_MINIO_DOMAINNEXT_PUBLIC_MINIO_PORT(可选)NEXT_PUBLIC_MINIO_DISABLE_SSL = 1(禁用 SSL;默认使用 HTTPS)
2)创建具备仅限该 Bucket 读/写/列举/删除权限的用户,并设置私有凭据(不要带 NEXT_PUBLIC 前缀):
MINIO_ACCESS_KEYMINIO_SECRET_ACCESS_KEY
🐘 备选数据库提供商(实验特性)
- 通过设置
POSTGRES_URL切换到其他 Postgres 兼容(支持连接池)的提供商。 - 某些提供商需要禁用 SSL,可设置:
DISABLE_POSTGRES_SSL = 1。
Supabase 示例:
- 连接串需使用“事务模式(Transaction Mode)”,端口为
6543 - 设置
DISABLE_POSTGRES_SSL = 1
🌐 国际化(I18N)
- 通过
NEXT_PUBLIC_LOCALE指定前台(非管理端)文案语言。 - 当前内置语言:
bd-bn、en-gb、en-us、id-id、pt-br、pt-pt、tr-tr、zh-cn
❓ 常见问题(FAQ)
-
为什么照片修改后没有立刻生效?
- 首页与网格页默认使用静态优化,变更可能需要几分钟才能传播。可以到
/admin/configuration点击「Clear Cache」清理缓存。
- 首页与网格页默认使用静态优化,变更可能需要几分钟才能传播。可以到
-
启用静态优化后,为什么生产环境构建失败?
- 有报告显示:大于 30MB 的大图或在 Vercel 前使用 CDN(例如 Cloudflare)可能会影响稳定性。可暂时关闭相关静态优化开关或参考 Issues #184/#185。
-
为什么旧照片的效果/数据不一致?
- 历史版本在模糊、镜头、AI/隐私等方面的处理方式有所演进。可在照片旁点击同步按钮,或前往
/admin/photos/updates批量同步需要更新的照片。
- 历史版本在模糊、镜头、AI/隐私等方面的处理方式有所演进。可在照片旁点击同步按钮,或前往
-
分享链接时 OG 图不显示?
- iMessage、Slack、X 等平台抓取超时很敏感。建议开启
NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTOS = 1与NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_OG_IMAGES = 1预渲染,但这会增加平台用量。
- iMessage、Slack、X 等平台抓取超时很敏感。建议开启
-
网格缩略图太小?
- 网格密度由长宽比决定(≤1 的比例每行更多)。可设置
NEXT_PUBLIC_GRID_ASPECT_RATIO或强制大缩略图NEXT_PUBLIC_SHOW_LARGE_THUMBNAILS = 1。
- 网格密度由长宽比决定(≤1 的比例每行更多)。可设置
-
标记为私密的照片是否绝对安全?
- 私密路径需要登录才能访问,但对象存储直链仍然可被访问(具有不可预测的随机 URL)。请谨慎对待。
-
本地开发是否必须依赖外部对象存储?
- 目前是的。若你有可行策略能在本地模拟外部存储并配合
next/image调试,欢迎提交 PR。
- 目前是的。若你有可行策略能在本地模拟外部存储并配合
-
是否支持 HEIC?
- 目前
sharp与next/image尚不支持直接处理 HEIC。通过 Apple 系统的分享面板上传会自动转为 JPG。详见相关 issue 讨论。
- 目前
-
Fujifilm 模拟为何没有和 EXIF 一起导入?
- 模拟数据存在厂商私有 Makernote 区。中间处理(如 iOS 裁剪)可能导致数据缺失。尽量使用相机原始文件,或在后台手动选择。
-
启用 AI 后连不上 OpenAI?
- 需要预先充值/购买额度;如自定义了 API Key 权限,请确保开启 Responses API 写权限。
🧪 脚本与命令
- 主要命令(见
package.json):pnpm dev:开发模式(Next.js + Turbopack)pnpm build:构建pnpm start:启动生产服务pnpm lint:ESLint 检查pnpm test:Jest 测试(watch 模式)pnpm analyze:打包分析(ANALYZE=true next build)
🔐 关键环境变量速查
- 认证与管理员:
AUTH_SECRET、ADMIN_EMAIL、ADMIN_PASSWORD - 站点域名:
NEXT_PUBLIC_DOMAIN - 数据库:
POSTGRES_URL、DISABLE_POSTGRES_SSL - 存储(四选一):Vercel Blob(
BLOB_READ_WRITE_TOKEN)、Cloudflare R2、AWS S3、MinIO(详见上文各自变量) - AI:
OPENAI_SECRET_KEY、OPENAI_BASE_URL(可选自定义) - 本地化:
NEXT_PUBLIC_LOCALE
✅ 建议的验证步骤
- 部署完成后,访问首页确认能打开
- 访问
/admin使用管理员账号登录 - 上传一张测试照片,查看照片详情与 EXIF
- 如启用了 AI,验证上传后是否能生成标题/描述/标签
- 配置自定义导航标题/简介等,确认能在前台显示
如遇问题,可以在后台的「Configuration」页点击「Clear Cache」再尝试,或前往上游仓库提交 issue。
祝使用顺利,拍摄愉快!📸
评论区