YN Blog
YN Blog - 现代化博客平台
一个基于 Next.js 16 和 Supabase 构建的功能完整、开箱即用的现代化博客系统。具备完整的前后台功能、嵌套评论系统、SEO 优化、深色模式、响应式设计,以及企业级安全防护(RLS 行级安全、Rate Limiting、XSS 防护、审计日志)。
无需编写一行代码,即可拥有一个功能完整的个人博客。
📚 目录
- 项目介绍
- 功能特性
- 技术栈
- 架构总览
- 快速开始(5 分钟上手)
- 环境要求
- 详细部署指南
- Supabase 存储配置
- Cloudflare R2 音频存储配置
- Supabase RLS 策略详解
- 环境变量说明
- 开发指南
- 常见问题与排障
- 贡献指南
- 许可证
🌟 项目介绍
YN Blog 是一个面向个人博主、技术写作者和小型内容团队的博客系统。它将 Next.js 的服务端渲染能力与 Supabase 的后端服务(PostgreSQL 数据库、Auth 认证、Storage 文件存储)相结合,提供从内容创作、发布到读者互动的完整闭环。
适合人群
- 想搭建个人博客的技术爱好者
- 想学习 Next.js App Router 与 Supabase 全栈开发的开发者
- 需要一个轻量级、可自托管 CMS 的内容创作者
核心亮点
- 全栈一体化:前端、后端、数据库、存储、认证一站式集成
- 服务端渲染:基于 Next.js App Router,首屏快、SEO 友好
- 行级安全:Supabase RLS 策略保证数据隔离,权限细化到行
- 渐进增强:Redis 缺失时自动降级到内存限流,开发零配置
- 安全加固:CSP 安全头、XSS 过滤、文件魔数校验、IP 限流、审计日志
✨ 功能特性
🏠 前台功能(访客使用)
| 功能 | 描述 |
|---|---|
| 首页展示 | 最新文章、热门文章、分类导航、标签云、公告、Hero 横幅 |
| 文章详情 | Markdown 渲染、代码高亮、阅读进度条、目录导航、分享按钮、图片灯箱 |
| 文章列表 | 分页浏览、封面图展示、阅读时间预估(预计算存储) |
| 评论系统 | 嵌套回复(最多 5 层)、登录后评论、评论审核、编辑标记 |
| 全文搜索 | PostgreSQL GIN 索引 + ts_rank 排序的全文检索 |
| 分类 / 标签 | 按分类或标签筛选文章 |
| 友链页面 | 友情链接展示、在线友链申请提交 |
| 公告系统 | 站点公告弹窗(防 Strict Mode 重复执行)和公告列表页 |
| 关于页面 | 站点介绍 |
| 作者主页 | 查看作者资料和文章列表 |
| 用户系统 | 注册登录(支持后台开关)、邮箱验证、OAuth 社交登录、个人中心、用户设置 |
| 隐私政策 | 隐私政策说明页面 |
| RSS 订阅 | 支持 RSS/Atom 订阅源 |
| SEO 优化 | Meta Tags、Open Graph、Twitter Card、JSON-LD 结构化数据、sitemap、robots |
| 深色模式 | 系统跟随 / 手动切换,无闪烁初始化 |
| 页面动画 | Framer Motion 页面过渡、淡入效果、回到顶部 |
| 草稿预览 | 7 天有效期的预览 token 分享 |
| 音乐播放 | 全站持久化音乐播放器,支持封面展示、曲目切换、进度拖拽、音量调节、收起展开动画 |
🎛️ 后台管理(管理员 / 作者使用)
| 功能 | 描述 |
|---|---|
| 仪表盘 | 数据统计概览(1 分钟自动刷新,刷新时显示缓存内容) |
| 文章管理 | 新增、编辑、删除、发布 / 草稿 / 归档、定时发布、预览 token |
| 分类管理 | 增删改查文章分类 |
| 标签管理 | 增删改查文章标签 |
| 评论管理 | 审核、通过、拒绝、删除评论 |
| 媒体管理 | 图片上传(自动压缩为 WebP)、列表查看、删除 |
| 用户管理 | 用户列表、角色管理、编辑用户信息 |
| 邮箱白名单 | 限制注册邮箱域名 |
| 站点设置 | 网站信息、SEO 配置、Hero 横幅、公告开关、账号注册开关 |
| 友链管理 | 增删改查友情链接 |
| 友链申请 | 审核、通过、拒绝友链申请 |
| 角色申请 | 处理用户的角色升级申请 |
| 公告管理 | 发布、置顶、管理站点公告 |
| 消息管理 | 查看和管理用户留言 |
| 订阅者管理 | 邮件订阅者列表、状态切换 |
| 曲目管理 | 新增、编辑、删除曲目,封面上传,排序调整 |
| 审计日志 | 记录文章状态变更、配置变更等操作 |
| 清理日志 | 查看过期未激活用户自动清理记录 |
👥 用户与权限系统
| 角色 | 权限范围 |
|---|---|
| User | 阅读文章、评论、收藏文章、提交留言 / 友链申请 / 角色申请 |
| Author | User 权限 + 创建 / 编辑 / 删除自己的文章、上传媒体 |
| Editor | Author 权限 + 管理所有文章、审核评论、管理分类 / 标签 / 友链 / 媒体 |
| Admin | 全部权限,含用户管理、站点设置、邮箱白名单、审计日志 |
🔒 安全特性
| 特性 | 描述 |
|---|---|
| Supabase RLS | 所有表启用行级安全策略,按角色隔离数据 |
| JWT 角色同步 | sync_role_to_jwt() 触发器将角色写入 JWT,RLS 零 IO 读取 |
| 中间件鉴权 | middleware.ts 路由权限保护与重定向 |
| XSS 防护 | 输入输出安全过滤、URL 协议校验、评论内容实体编码 |
| SQL 注入防护 | 参数化查询 + RPC 函数封装 |
| Rate Limiting | Redis + 内存双通道限流(订阅、登录、上传、删除等) |
| 安全请求头 | CSP、HSTS、X-Frame-Options、X-Content-Type-Options、Permissions-Policy |
| 文件上传安全 | MIME 类型 + 文件头魔数双重校验,防止伪装文件 |
| 计数器保护 | 数据库触发器禁止非特权用户直接修改浏览 / 点赞数 |
| 审计日志 | 记录文章状态变更与配置变更行为 |
| 邮箱白名单 | 限制注册邮箱域名 |
| 会话撤销 | 角色变更后自动撤销旧 JWT 会话,强制重新登录 |
🛠️ 技术栈
前端
| 技术 | 版本 | 用途 |
|---|---|---|
| Next.js | 16 | React 全栈框架,App Router + Turbopack |
| React | 19 | UI 库 |
| TypeScript | 5 | 类型安全 |
| Tailwind CSS | 3 | 实用优先 CSS 框架 |
| Framer Motion | 12 | 动画库 |
| Lucide React | 1 | 图标库 |
| React Markdown | 10 | Markdown 渲染(支持 GFM、代码高亮、图片灯箱) |
后端服务
| 服务 | 用途 |
|---|---|
| Supabase Auth | 用户认证(邮箱密码 + OAuth 社交登录) |
| Supabase Database | PostgreSQL 数据库(含 RLS、RPC、触发器、全文搜索) |
| Supabase Storage | 文件存储(图片、PDF) |
| Cloudflare R2 | 音频文件存储 + Worker 代理(直传/播放) |
| Redis | 生产环境限流(多实例部署必需;开发环境可降级为内存限流) |
| Sentry | 错误监控(可选,未配置时自动 no-op) |
| Sharp | 图片压缩与格式转换(服务端) |
🏗️ 架构总览
项目采用分层架构,职责清晰:
请求 → middleware.ts(鉴权/重定向)
↓
App Router 页面(Server Components)
↓
Server Actions(@/actions) ← 表单提交 / 数据变更
↓
Services 业务层(@/services) ← 业务逻辑
↓
Repositories 数据层(@/repositories) ← 数据访问
↓
Supabase Client(admin / browser)
分层说明:
src/app:Next.js App Router 页面与路由src/app/profiles/[id]/:统一用户/作者主页(替代旧的/authors/[id])
src/actions:Server Actions,处理表单提交与数据变更src/services:业务逻辑层,所有文件含import "server-only"src/repositories:数据访问层,封装 Supabase 查询src/lib:通用工具(鉴权、缓存、限流、日志、验证等)src/components:React 组件(按admin/、ui/、music/、layout/等分类)src/components/music/:音乐播放器组件(PersistentMusicPlayer、MiniMusicPlayer 等)
🚀 快速开始(5 分钟上手)
环境要求
| 工具 | 版本 | 说明 |
|---|---|---|
| Node.js | ≥ 18.17 | 推荐 LTS 版本 |
| npm | 随 Node.js 安装 | 或使用 pnpm / yarn |
| Git | 任意版本 | 代码管理 |
| Supabase 账号 | 免费 | 注册 supabase.com |
关于 Redis:生产环境必需(支持多实例部署的限流);本地开发可不配置,自动降级为内存限流。
第一步:获取代码
git clone https://github.com/你的用户名/yn-blog.git
cd yn-blog
或访问 GitHub 仓库 → 「Code」→「Download ZIP」→ 解压后进入目录。
第二步:安装依赖
npm install
国内网络较慢时使用镜像:
npm install --registry=https://registry.npmmirror.com
第三步:配置 Supabase
3.1 注册并创建项目
- 访问 supabase.com,注册登录
- 点击「New Project」创建新项目
- 填写:项目名称、数据库密码、选择离你最近的区域
- 等待约 2 分钟项目初始化完成
3.2 获取项目凭据
进入项目 → 左侧「Project Settings」→「API」,记录:
- Project URL(形如
https://xxxxxx.supabase.co) - anon public 密钥
- service_role secret 密钥(切勿泄露)
3.3 配置环境变量
复制 .env.example 为 .env.local,填入凭据:
NEXT_PUBLIC_SUPABASE_URL=https://你的项目ID.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=你的anon密钥
SUPABASE_SERVICE_ROLE_KEY=你的service密钥
NEXT_PUBLIC_SITE_URL=http://localhost:3000
第四步:初始化数据库
- 在 Supabase 项目中点击左侧「SQL Editor」→「New query」
- 打开项目目录下的
supabase/init.sql,复制全部内容粘贴到编辑器 - 点击「Run」执行
- 看到输出
Database initialization complete!即成功
该脚本会创建所有表、索引、触发器、RPC 函数、RLS 策略、存储桶,并初始化默认设置和白名单域名。
第五步:启动项目
npm run dev
打开浏览器访问 http://localhost:3000 即可看到博客首页。
第六步:创建管理员账号
- 访问 http://localhost:3000/register 注册账号并登录
- 回到 Supabase →「Table Editor」→ 找到
profiles表 - 找到你刚注册的用户,将
role字段值改为admin - 退出博客并重新登录(让 JWT 更新角色)
- 访问 http://localhost:3000/admin 进入后台
📦 详细部署指南
推荐部署平台:Vercel(免费)
准备工作
- 代码已推送到 GitHub
- 注册 Vercel 账号
部署步骤
- 访问 Vercel Dashboard →「Add New」→「Project」
- 选择你的 GitHub 仓库 →「Import」
- 在配置页面填写:
- Framework Preset:自动识别为 Next.js
- Root Directory:保持默认
- Environment Variables:依次添加
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEYNEXT_PUBLIC_SITE_URL(例如https://your-blog.vercel.app)REDIS_URL(推荐配置,格式redis://:password@host:port,可使用 Upstash、Redis Cloud 等托管服务)
- 点击「Deploy」,等待 2-3 分钟构建完成
部署后更新 Supabase 配置
- 回到 Supabase →「Authentication」→「URL Configuration」
- 将「Site URL」更新为你的 Vercel 域名
- 在「Redirect URLs」中添加:
https://你的域名/auth/callbackhttp://localhost:3000/auth/callback(保留本地开发)
可选:配置 Sentry 错误监控
- 注册 Sentry 账号并创建 Next.js 项目
- 在 Vercel 环境变量中添加:
NEXT_PUBLIC_SENTRY_DSNSENTRY_ORGSENTRY_PROJECTSENTRY_AUTH_TOKEN
- 重新部署,Sentry 自动接管错误上报
未配置
NEXT_PUBLIC_SENTRY_DSN时,Sentry 完全 no-op,不影响应用功能。
其他部署方式
项目也支持部署到任何支持 Next.js 的平台(如自托管 Node.js 服务器、Docker 等)。部署时需确保:
- Node.js ≥ 18.17 运行环境
- 所有环境变量已正确注入
- Redis 实例可访问(生产环境)
- Supabase 项目的「Redirect URLs」已包含你的域名回调路径
🗄️ Supabase 存储配置
本项目使用单个 media 存储桶管理所有用户上传的文件(文章封面、图片、PDF 等)。
存储桶配置
supabase/init.sql 已自动创建 media 存储桶:
| 配置项 | 值 |
|---|---|
| 桶名称 | media |
| 公开访问 | 是(public) |
| 文件大小上限 | 10 MB |
| 允许的 MIME 类型 | image/jpeg、image/png、image/gif、image/webp、image/svg+xml |
存储桶策略
| 策略 | 操作 | 条件 |
|---|---|---|
| Public can read media files | SELECT | bucket_id = 'media' |
| Authenticated users can upload media | INSERT | bucket_id = 'media' AND auth.uid() IS NOT NULL |
| Users can delete own media files | DELETE | bucket_id = 'media' AND owner = auth.uid() |
关于 Storage 安全警告
在 Supabase Dashboard 的 Storage 页面,可能看到警告:
"Clients can list all files in this bucket"
此警告可安全忽略,原因:
- 本项目不直接从 Storage 桶读取文件列表
- 文件元数据存储在
media_files数据库表中 - 通过
media_files表的 RLS 策略控制访问 - Storage 桶仅用于文件存储与公开读取
建议点击警告右侧「Remove policy」移除该策略以提升安全性。
上传处理流程
- 前端提交文件到
handleUploadFileServer Action - 校验文件大小、MIME 类型、文件头魔数(防伪装攻击)
- 图片使用
sharp自动压缩并转换为 WebP / PNG 格式 - PDF 校验文件头
%PDF- - 通过 service role 上传到
media桶 - 文件元数据写入
media_files表
☁️ Cloudflare R2 音频存储配置
本项目使用 Cloudflare R2 存储音频文件,通过 Cloudflare Worker 实现客户端直传和代理播放。
架构说明
客户端 → Cloudflare Worker(上传/删除/播放代理)
↓
Cloudflare R2 Bucket(音频文件存储)
配置步骤
1. 创建 R2 Bucket
- 登录 Cloudflare Dashboard
- 进入「R2」→「Create Bucket」
- 填写 Bucket 名称(如
yn-blog-audio) - 记录 Bucket 名称
2. 创建 Worker 脚本
- 进入「Workers & Pages」→「Create Application」→「Worker」
- 从项目目录
cloudflare/worker.js复制全部内容 - 粘贴到 Worker 编辑器中
- 绑定 R2 Bucket:在 Worker Settings → Bindings → R2 Bucket Bindings 中添加
- Variable name:
AUDIO_BUCKET - Bucket name: 你的 R2 Bucket 名称
- Variable name:
- 设置 Secret:
- Variable name:
AUDIO_UPLOAD_KEY - Value: 运行
openssl rand -base64 48生成的随机密钥
- Variable name:
3. 配置环境变量
在项目 .env.local 中添加:
NEXT_PUBLIC_AUDIO_CDN_URL=https://你的-worker-name.your-subdomain.workers.dev
NEXT_PUBLIC_AUDIO_UPLOAD_KEY=与Worker中AUDIO_UPLOAD_KEY相同的密钥
AUDIO_UPLOAD_KEY=同上(服务端删除文件用)
安全说明
- Worker 通过
AUDIO_UPLOAD_KEY验证上传/删除请求 - 播放请求直接返回 R2 预签名 URL,不经过 Worker 代理
- 前端通过
navigator.onLine检测离线状态,离线时隐藏播放器
🔐 Supabase RLS 策略详解
为什么需要 RLS 策略
RLS(Row Level Security)是 Supabase 提供的行级安全功能,确保:
- 用户只能访问和修改自己的数据
- 不同角色拥有不同权限
- 防止未授权的数据访问
本项目所有表均已启用 RLS,策略已包含在 supabase/init.sql 中,执行脚本即可自动创建。
JWT 角色检查机制
本项目采用基于 JWT 的角色检查(app_meta_role() 函数),替代传统的 profiles 表子查询:
sync_role_to_jwt()触发器在profiles.role变更时,自动将新角色写入auth.users.raw_app_meta_data.role- 同时撤销该用户的所有会话,强制重新登录获取含新角色的 JWT
- RLS 策略通过
app_meta_role()直接读取 JWT,零 IO,性能更优
策略清单(主要表)
profiles 表(用户资料)
| 策略 | 权限 | 说明 |
|---|---|---|
| Public profiles are viewable by everyone | SELECT | 所有人可查看公开字段 |
| Users can update their own profile | UPDATE | 用户只能更新自己的资料 |
| Admin can manage all profiles | ALL | 管理员可管理所有用户资料 |
posts 表(文章)
| 策略 | 权限 | 说明 |
|---|---|---|
| Published posts are viewable by everyone | SELECT | 所有人可查看已发布文章 |
| posts_preview_token_select | SELECT | 预览 token 非空时可访问(草稿分享) |
| Users can view their own draft posts | SELECT | 用户可查看自己的草稿 |
| Author can create posts | INSERT | 作者可创建文章 |
| Author can update their own posts | UPDATE | 作者 / 编辑可更新文章 |
| Author can delete their own posts | DELETE | 作者 / 编辑可删除文章 |
| Admin can manage all posts | ALL | 管理员可管理所有文章 |
tracks 表(音乐曲目)
| 策略 | 权限 | 说明 |
|---|---|---|
| Active tracks are viewable by everyone | SELECT | 所有人可查看已启用的曲目 |
| Admin can manage all tracks | ALL | 管理员 / 编辑可管理所有曲目 |
comments 表(评论)
| 策略 | 权限 | 说明 |
|---|---|---|
| Approved comments are viewable by everyone | SELECT | 所有人可查看已审核评论 |
| Users can view their own comments | SELECT | 用户可查看自己所有评论 |
| Authenticated users can create comments | INSERT | 认证用户可创建评论(仅 pending 状态) |
| Users can update their own comments | UPDATE | 用户可更新自己的评论(仅 pending 状态) |
| Users can delete their own comments | DELETE | 用户可删除自己的评论 |
| Admin can manage all comments | ALL | 管理员 / 编辑可管理所有评论 |
其他表策略
categories、tags、media_files、site_settings、favorites、audit_logs、email_whitelist、user_cleanup_logs、role_applications、messages、announcements、links、link_applications、subscribers、tracks 等表均已配置对应 RLS 策略,详见 supabase/init.sql 文件。
验证策略是否生效
方法 1:Supabase Dashboard
进入「Table Editor」→ 选择表 →「Policies」标签 → 查看策略列表。
方法 2:SQL 查询
SELECT
schemaname, tablename, policyname, permissive, roles, cmd, qual, with_check
FROM pg_policies
WHERE schemaname = 'public'
ORDER BY tablename, policyname;
方法 3:实际测试
使用普通用户登录,尝试访问其他用户数据,确认被正确阻止。
角色权限矩阵
| 功能 | User | Author | Editor | Admin |
|---|---|---|---|---|
| 阅读已发布文章 | ✅ | ✅ | ✅ | ✅ |
| 创建文章 | ❌ | ✅ | ✅ | ✅ |
| 编辑自己的文章 | ❌ | ✅ | ✅ | ✅ |
| 编辑所有文章 | ❌ | ❌ | ✅ | ✅ |
| 删除自己的文章 | ❌ | ✅ | ✅ | ✅ |
| 删除所有文章 | ❌ | ❌ | ✅ | ✅ |
| 管理分类 / 标签 | ❌ | ❌ | ✅ | ✅ |
| 审核评论 | ❌ | ❌ | ✅ | ✅ |
| 管理用户 | ❌ | ❌ | ❌ | ✅ |
| 站点设置 | ❌ | ❌ | ❌ | ✅ |
⚙️ 环境变量说明
必需变量
| 变量 | 说明 | 示例 |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | Supabase 项目 URL | https://xxxx.supabase.co |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Supabase anon 公钥 | eyJ... |
SUPABASE_SERVICE_ROLE_KEY | Supabase service role 密钥(服务端使用,切勿泄露) | eyJ... |
NEXT_PUBLIC_SITE_URL | 站点访问地址(本地开发用 http://localhost:3000) | https://your-blog.vercel.app |
Redis 配置(生产环境必需)
| 变量 | 说明 | 示例 |
|---|---|---|
REDIS_URL | Redis 连接字符串 | redis://:password@host:6379 |
REDIS_PASSWORD | Redis 密码(可选,若 URL 中已含可省略) | your-password |
REDIS_DB | Redis 数据库编号(可选,默认 0) | 0 |
Redis 用于:订阅限流、登录限流、上传限流、删除限流、链接申请限流等。未配置时自动降级为内存限流(仅适用于单实例部署)。
音乐音频配置(可选)
| 变量 | 说明 | 示例 |
|---|---|---|
NEXT_PUBLIC_AUDIO_CDN_URL | Cloudflare Worker 音频代理地址 | https://audio.ynpro.top |
NEXT_PUBLIC_AUDIO_UPLOAD_KEY | R2 Worker 上传鉴权密钥(客户端直传用) | openssl rand -base64 48 |
AUDIO_UPLOAD_KEY | R2 Worker 上传/删除鉴权密钥(服务端用) | 同上 |
音乐功能使用 Cloudflare R2 存储音频文件,通过 Worker 实现客户端直传和代理播放。未配置时音乐播放器仍可显示但不支持上传新曲目。
可选变量
| 变量 | 说明 |
|---|---|
GOOGLE_VERIFICATION_CODE | Google Search Console 验证码 |
NEXT_PUBLIC_ADMIN_EMAIL | 管理员联系邮箱(社交链接默认值) |
TRUSTED_PROXY_HEADER | 受信任的代理转发头(逗号分隔) |
TRUST_X_FORWARDED_FOR | 是否信任 x-forwarded-for 头(Vercel 生产默认 false,开发默认 true) |
Sentry 配置(可选)
| 变量 | 说明 |
|---|---|
NEXT_PUBLIC_SENTRY_DSN | Sentry DSN(客户端 + 服务端共用) |
SENTRY_ORG | Sentry 组织 slug |
SENTRY_PROJECT | Sentry 项目 slug |
SENTRY_AUTH_TOKEN | Sentry 认证令牌(用于 source map 上传;未设置时跳过 webpack 插件) |
未配置
NEXT_PUBLIC_SENTRY_DSN时,Sentry 完全 no-op,不影响应用功能。
👨💻 开发指南
可用命令
| 命令 | 说明 |
|---|---|
npm run dev | 启动开发服务器(Turbopack) |
npm run build | 构建生产版本 |
npm run start | 启动生产服务器 |
npm run lint | 运行 ESLint 代码检查 |
npm run test | 运行所有单元测试 |
npm run test:watch | 监听模式运行测试 |
npm run test:coverage | 运行测试并生成覆盖率报告 |
npm run test:seo | 运行 SEO 元数据测试 |
npm run seo:validate | 运行 SEO 校验脚本 |
项目结构
yn-blog/
├── public/ # 静态资源(favicon、OG 图、赞助码等)
├── cloudflare/ # Cloudflare Worker 脚本(音频上传/删除代理)
├── src/
│ ├── actions/ # Server Actions(服务端操作)
│ │ └── __tests__/ # Action 单元测试
│ ├── app/ # Next.js App Router(页面路由)
│ │ ├── admin/ # 后台管理页面
│ │ │ └── tracks/ # 曲目管理页面
│ │ ├── api/ # API 路由(健康检查、统计等)
│ │ ├── auth/ # 认证页面(登录、注册、回调)
│ │ ├── profiles/ # 统一用户/作者主页
│ │ ├── posts/ # 文章页面
│ │ └── ... # 其他页面
│ ├── components/ # React 组件
│ │ ├── admin/ # 后台管理组件
│ │ ├── hero/ # 首页横幅组件
│ │ ├── layout/ # 布局组件(Header / Footer)
│ │ ├── music/ # 音乐播放器组件
│ │ ├── ui/ # 基础 UI 组件
│ │ └── ... # 功能组件
│ ├── constants/ # 常量定义
│ ├── contexts/ # React Context
│ ├── hooks/ # 自定义 Hooks
│ │ └── admin/ # 后台管理 Hooks
│ ├── lib/ # 工具库
│ │ ├── supabase/ # Supabase 客户端
│ │ └── ... # 鉴权、缓存、限流、日志等
│ ├── repositories/ # 数据访问层
│ │ └── __tests__/ # Repository 单元测试
│ ├── services/ # 业务逻辑层
│ ├── test/ # 测试基础设施
│ │ ├── mocks/ # 测试 Mock
│ │ ├── security/ # 安全测试
│ │ └── seo/ # SEO 测试
│ ├── types/ # TypeScript 类型定义
│ ├── utils/ # 工具函数
│ │ └── validators/ # 验证器
│ └── middleware.ts # 路由鉴权中间件
├── supabase/
│ ├── init.sql # 数据库初始化脚本(含表、索引、RLS、存储桶、tracks 表)
│ └── migrations/ # 增量迁移脚本(用于已部署库的版本追踪)
├── instrumentation.ts # Next.js Instrumentation(Sentry 集成)
├── sentry.*.config.ts # Sentry 多运行时配置(client / server / edge)
├── next.config.mjs # Next.js 配置(图片优化、安全头、缓存)
├── tailwind.config.ts # Tailwind 配置
├── vitest.config.ts # Vitest 测试配置
└── tsconfig.json # TypeScript 配置
开发约定
- Server Actions 必须使用 Zod schema 校验所有输入
- Service 层文件必须包含
import "server-only"防止客户端导入 - 后台页面统一使用
AutoRefreshServer包装实现 1 分钟自动刷新 - 加载状态统一使用
LoadingSpinner显示「加载中...」 - 返回按钮使用
router.back()返回上一页 - 评论回复占位符动态显示「回复:{username}:{评论内容}」
- 草稿预览token 有效期 7 天,存储于
preview_token_expires_at字段 - 文章阅读时间预计算并存储在
posts.reading_time列 - 根布局不调用
getAuthState()或cookies(),认证状态在客户端水合后获取 - 用户主页统一使用
/profiles/[id]路由(替代旧的/authors/[id]) - 新标签页链接不触发全屏加载动画(通过 Ctrl/Shift/Meta 键或中键检测)
- 加载状态在页面可见性变为 hidden 时自动停止(防止后台标签页无限加载)
❓ 常见问题与排障
1. npm install 失败
现象:安装依赖时卡住或报错
排查步骤:
- 检查 Node.js 版本:
node -v,需 ≥ 18.17 - 删除
node_modules和package-lock.json - 重新运行
npm install - 网络慢时使用镜像:
npm install --registry=https://registry.npmmirror.com - 清除 npm 缓存:
npm cache clean --force
2. 数据库连接失败
现象:Failed to connect to Supabase 或类似错误
排查步骤:
- 检查
.env.local中NEXT_PUBLIC_SUPABASE_URL和密钥是否正确 - 确认值两端没有多余空格或引号
- 确认 Supabase 项目未暂停(免费项目闲置一周会自动暂停)
- 检查网络能否访问 Supabase 域名
- 确认
SUPABASE_SERVICE_ROLE_KEY与NEXT_PUBLIC_SUPABASE_ANON_KEY未填反
3. 图片上传失败
现象:上传图片后显示不出来或报错
排查步骤:
- 确认 Storage 中存在
media桶(执行init.sql会自动创建) - 确认桶为「Public」属性
- 检查
SUPABASE_SERVICE_ROLE_KEY是否正确 - 检查文件大小是否超过 10 MB 限制
- 检查文件 MIME 类型是否在允许列表中
- 查看浏览器控制台与服务端日志的具体错误
4. 评论无法提交
现象:点击提交评论没反应或报错
排查步骤:
- 确认用户已登录
- 检查
comments表是否创建成功(执行init.sql) - 检查 RLS 策略是否正确配置
- 查看浏览器控制台错误信息
- 确认评论内容非空且未触发限流
5. 后台管理页面无权限
现象:访问 /admin 被重定向或提示无权限
排查步骤:
- 确认已登录
- 在 Supabase「Table Editor」中查看
profiles表,确认你的role为admin - 退出并重新登录(让 JWT 包含新角色;
sync_role_to_jwt()会撤销旧会话) - 清除浏览器 cookie 后重新登录
- 检查中间件日志是否有异常
6. 部署后图片无法加载
现象:Vercel 部署后图片显示裂图
排查步骤:
- 检查
next.config.mjs中images.remotePatterns是否包含你的图片域名 - 确认 Supabase Storage 的 CORS 设置包含你的域名
- 检查图片 URL 是否正确
- 确认图片未过期或被删除
7. 登录后跳回 localhost
现象:生产环境登录后重定向到 localhost
排查步骤:
- 确认
NEXT_PUBLIC_SITE_URL环境变量已设为生产域名 - 在 Supabase「Authentication」→「URL Configuration」中更新「Site URL」为生产域名
- 在「Redirect URLs」中添加
https://你的域名/auth/callback
8. OAuth 社交登录 404
现象:GitHub / Google 登录回调返回 404
排查步骤:
- 在 Supabase「Authentication」→「URL Configuration」的「Redirect URLs」中添加
https://你的域名/auth/callback - 确认 OAuth Provider 已在 Supabase 中启用并配置了 Client ID / Secret
- 检查 OAuth 应用的回调 URL 是否指向 Supabase(非应用域名)
9. 限流未生效或误报
现象:接口频率限制异常
排查步骤:
- 生产环境:确认
REDIS_URL已配置且 Redis 实例可达 - 开发环境:未配置 Redis 时自动降级为内存限流(仅单实例有效)
- 检查 Vercel 函数日志中是否有 Redis 连接错误
- 确认限流 key 的维度(IP / 用户 ID)符合预期
10. 构建失败:Turbopack 相关错误
现象:npm run build 报错
排查步骤:
- 确认 Node.js 版本 ≥ 18.17
- 删除
.next目录后重新构建 - 检查是否有 Server Component 中误用了
<script>标签(Next.js 16 + Turbopack 不允许) - 运行
npm run lint检查代码问题 - 检查环境变量是否完整配置
11. 角色变更后权限未更新
现象:升级用户角色后,用户仍无对应权限
原因:sync_role_to_jwt() 触发器会撤销旧会话,需重新登录获取新 JWT
解决:让用户退出并重新登录即可。
12. 后台数据不更新
现象:编辑文章后列表未刷新
排查步骤:
- 后台页面使用 1 分钟自动刷新,等待或手动刷新页面
- 检查 Server Action 是否调用了
revalidatePath - 开发环境重启 dev server
- 检查浏览器是否缓存了旧数据(强制刷新 Ctrl+F5)
13. 音乐播放器无法播放音频
现象:点击播放无声音或报错
排查步骤:
- 检查
NEXT_PUBLIC_AUDIO_CDN_URL环境变量是否正确配置 - 确认 Cloudflare Worker 已部署且正常运行
- 检查 R2 Bucket 中是否有对应的音频文件
- 查看浏览器控制台是否有 CORS 错误
- 确认
next.config.mjs中 CSP 的media-src和connect-src已包含音频域名 - 检查
navigator.onLine状态,离线模式下播放器会自动隐藏
14. 曲目管理页面 403 无权限
现象:访问 /admin/tracks 提示无权限
排查步骤:
- 确认当前用户角色为
admin或editor - 在 Supabase「Table Editor」中查看
profiles表,确认role字段正确 - 退出并重新登录(让 JWT 包含新角色)
🤝 贡献指南
欢迎提交 Issue 和 Pull Request!
贡献流程
- Fork 本仓库
- 创建特性分支:
git checkout -b feature/awesome-feature - 提交更改:
git commit -m 'Add some awesome feature' - 推送分支:
git push origin feature/awesome-feature - 创建 Pull Request
开发规范
- 提交前运行
npm run lint和npm run test确保通过 - 遵循现有代码风格(TypeScript + 函数式组件)
- 新增功能需附带单元测试
- 新增 Server Action 必须使用 Zod 校验输入
- 新增 Service 文件必须包含
import "server-only" - 数据库变更需同步更新
supabase/init.sql - 曲目管理变更需同步更新
supabase/migrations/中的迁移脚本 - Cloudflare Worker 脚本位于
cloudflare/worker.js,修改后需同步部署到 Cloudflare
📄 许可证
MIT License - 详见 LICENSE 文件。
💬 获取帮助
如果遇到问题:
- 查看上方「常见问题与排障」部分
- 检查浏览器控制台与服务端日志
- 提交 GitHub Issue,附上错误信息与复现步骤
🙏 致谢
感谢以下优秀的开源项目:
❤️ 支持与赞助
如果这个项目对你有帮助,欢迎请作者喝杯咖啡,支持持续维护与开发。
(上) 支付宝 / (下) 微信
如果这个项目对你有帮助,请给个 Star ⭐ 支持一下!
Built with ❤️

一名热爱技术、专注于分享知识的开发者。如果您喜欢我的文章,欢迎关注并订阅 RSS。
相关推荐
文案
简易贪吃蛇

