Next.js 16 + Supabase 的开源全栈 CMS:NextBlock 实战解析
当业务里需要一套轻量、可控的内容管理系统,又不想被重型 CMS 绑定的时候,开源项目往往是最合适的切入点。最近在调研 Next.js 全栈方案时,看到一个很有意思的项目:NextBlock CMS。它把 Next.js 16 和 Supabase 组合成一套开源的全栈 CMS,定位很清晰:不重复造轮子,利用 Next.js 的 App Router 能力,加上 Supabase 的 Postgres、Auth 和 Storage,快速搭建自带后台的内容站点。
本文就从实际使用角度,拆解 NextBlock CMS 的核心设计、环境准备、部署流程、二次开发方式,以及 Supabase Storage 接入和常见坑点。适合正在选型 CMS、想自己维护内容平台,或者准备用 Next.js + Supabase 做全栈项目的开发者阅读。
1. 背景与核心概念
1.1 什么是 NextBlock CMS
NextBlock CMS 是一套基于 Next.js 16 的开源全栈 CMS(Content Management System,内容管理系统)。它不是一个类似 WordPress 的独立服务,而是把内容管理能力以代码和数据结构的方式集成进 Next.js 应用里,让开发者既能获得 CMS 的编辑体验,又能保留 Next.js 的灵活性和部署方式。
这里有两个关键词值得拆开看:
- Next.js 16:当前版本已经全面拥抱 App Router 架构,支持服务端组件、服务端渲染、静态生成和增量静态再生成。NextBlock CMS 的内容渲染主要依赖服务端能力,因此内容的读取性能、SEO 友好度都有天然优势。
- Supabase:开源 Firebase 替代方案,底层是 PostgreSQL,提供了数据库、用户认证、存储、实时订阅和后端函数。CMS 的数据结构本质上是结构化内容,用 Postgres 存储非常合理;而图片、附件这类二进制文件,则交给 Supabase Storage。
简单来说,NextBlock CMS = Next.js 负责页面和渲染,Supabase 负责数据和文件,两者结合构成一个「代码即配置」的内容管理系统。
1.2 它解决什么问题
传统 CMS 开发中经常遇到的几个痛点:
- 内容模型搭建麻烦。很多 CMS 的后台拖拽非常灵活,但真要自定义字段和数据结构时,反而受平台约束。
- 前后端割裂。内容数据存在 A 系统,页面渲染在 B 系统,接口联调和权限控制都很繁琐。
- 部署链路重。传统 PHP CMS 需要配置服务器、数据库、Web Server,维护成本高。
- 技术栈不统一。开发人员需要在多个语言和框架之间反复切换。
NextBlock 的思路是把内容模型直接定义在代码里,数据库结构由迁移脚本或 Supabase 控制,渲染层完全交给 Next.js。这样的好处是:
- 内容结构版本可控,可以放进 Git 仓库审查。
- 数据模型扩展不需要额外后台系统,写代码即可。
- 页面渲染和内容管理在同一个项目里,部署就是一次 Serverless 部署。
1.3 典型应用场景
根据项目定位和生态,以下几个场景最适合使用 NextBlock CMS:
| 场景 | 适用原因 |
|---|---|
| 技术博客 / 文档站 | 需要 Markdown 或富文本内容管理,同时对 SEO 要求高 |
| 企业官网 | 页面区块化,运营人员可维护首页内容和新闻动态 |
| 个人作品集 | 数据结构灵活,不需要复杂的权限体系 |
| SaaS 产品帮助中心 | 内容分版本管理,与产品发版节奏一致 |
| 内部知识库 | 利用 Supabase Auth 做员工登录和权限控制 |
需要注意,NextBlock CMS 更像「面向开发者的轻量 CMS」,不是面向不懂技术的运营人员提供的所见即所得平台。它的目标用户仍是开发团队。
1.4 为什么要了解这类项目
现在 CMS 领域呈现两种方向:一种是 Strapi、Directus 这种偏重后台管理的「无头 CMS」;另一种是 Next.js + headless CMS 的前后端分离方案。而 NextBlock CMS 走的是一条更极客的路线:内容管理、数据库、文件存储、认证全部用开源服务组装,再以代码仓库作为唯一事实来源。这种思路的价值在于可控性和可迁移性——项目哪天不维护了,代码和数据库结构还在你自己手里,完全可以平移到其他技术栈。
另外,从学习角度看,这类项目也是理解全栈开发的好范本。它涉及服务端渲染、数据库表设计、文件存储策略、API 路由、权限控制、部署配置,几乎覆盖了现代 Web 开发的完整链路。
2. 环境准备与版本说明
2.1 开发环境要求
在开始之前,我们先确认本机环境。由于 NextBlock CMS 是较新的开源项目,且目标环境是 Next.js 16,建议按以下版本准备:
| 软件 | 建议版本 | 说明 |
|---|---|---|
| Node.js | 20.x 或更高 | Next.js 16 需要较新的 Node 运行时 |
| npm / pnpm | 最新稳定版 | 包管理器,pnpm 对 monorepo 支持更好 |
| Git | 最新版 | 拉取代码和版本管理 |
| Supabase CLI | 最新版 | 本地开发数据库和远程项目管理 |
| Docker | 可选 | Supabase CLI 本地启动依赖 Docker |
| 代码编辑器 | VS Code / Cursor | 安装 nextjs、sql、docker 插件更顺手 |
版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。Next.js 16 的正式特性以官方发布为准,如果使用的是 Next.js 15,部分 API 可能有差异,需要按实际版本调整。
2.2 Supabase 账号准备
NextBlock CMS 的核心数据层依赖 Supabase,所以需要提前准备:
- 访问 supabase.com 注册账号。
- 创建一个新组织(Organization)。
- 在组织下创建新项目,记录下项目的 URL 和 anon / service_role 两个 key。
- 安装 Supabase CLI,用于本地开发环境。
本地开发时,可以使用 Supabase CLI 启动一套完整的本地服务,包括 Postgres 数据库、Auth、Storage 和 API:
执行 supabase start 后,CLI 会启动本地容器并在终端输出本地 API 地址和 key。示例输出类似:
这里需要在 .env.local 中配置。
2.3 Next.js 项目基础概念
在接触 NextBlock CMS 之前,建议大家对 Next.js 的 App Router 有基本了解。几个核心概念:
app/ 目录:Next.js 13 之后引入的目录约定。app/page.tsx 对应首页路由,app/blog/[slug]/page.tsx 对应动态路由。
服务端组件与客户端组件:默认情况下,App Router 中的组件是服务端组件,可以直接访问数据库和文件系统。如果组件里需要 useState、useEffect 或浏览器事件,就需要在文件顶部加 "use client" 指令。
API 路由:在 App Router 中,app/api/xxx/route.ts 可以定义 GET、POST、PUT、DELETE 等接口。
Server Actions:允许在服务端直接修改数据的异步函数,常用来处理表单提交,适合 CMS 的内容创建和更新场景。
NextBlock CMS 的很多设计都在这些概念之上展开。理解它们之后,阅读项目源码会轻松很多。
3. NextBlock CMS 核心架构拆解
3.1 整体架构图
不画复杂的架构图,用文字描述整体的数据流:
内容发布流程:
这里要注意,NextBlock CMS 的内容存储和数据模型是放在 Supabase 里的,不是像 MDX 方案那样把内容写在本地文件里。这样设计的好处是内容更新不需要重新构建项目,适合网站部署后继续被运营人员维护的场景。
3.2 Post 内容模型设计
一个 CMS 最核心的数据模型就是「文章(Post)」。参考常见的 CMS 设计,以及 NextBlock 这类项目的思路,Post 表大致包含以下字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | uuid | 主键,默认 gen_random_uuid() |
| title | text | 标题 |
| slug | text | 访问路径,需唯一 |
| excerpt | text | 摘要或描述 |
| content | jsonb | 正文内容,以 JSON 结构承载块数据 |
| cover_image | text | 封面图地址 |
| featured | boolean | 是否精选 |
| status | text | 草稿/已发布 |
| author_id | uuid | 作者,关联用户表 |
| published_at | timestamptz | 发布时间 |
| created_at | timestamptz | 创建时间 |
| updated_at | timestamptz | 更新时间 |
其中 content 使用 jsonb 类型,是 NextBlock 这类区块化 CMS 的关键设计。jsonb 可以存储任意 JSON 结构,不需要为每种内容类型单独建表。
3.3 Block 区块模型
NextBlock CMS 的 Block 可以理解为内容页面的「积木」。比如一篇博客文章可能包含:
- 标题块
- 段落块
- 图片块
- 代码块
- 引用块
- 嵌入视频块
- 自定义组件块
每个 Block 在 content 字段中对应一个 JSON 对象,结构类似:
渲染的时候,Next.js 组件根据 type 字段匹配对应的渲染组件,把 props 传进去。
这种设计有几个明显优点:
- 扩展性强。要新增一种内容块,只需要新增一个组件并注册类型,数据库不需要迁移。
- 内容结构清晰。复杂页面可以被拆成可组合的区块,每个区块的职责单一。
- 前后端一致。编辑器的输出结构就是渲染层的输入结构,避免复杂的转换层。
对于站点开发者来说,这也是 NextBlock CMS 最有吸引力的地方:内容模型不再是「填空题」,而是「搭积木」。
3.4 Supabase 在架构中的职责
Supabase 在 NextBlock CMS 中承担了三个核心角色:
Postgres 数据库:存储 Post、Block、媒体文件元数据、用户信息等结构化数据。通过 PostgREST 自动生成 RESTful API,也支持直接通过 supabase-js SDK 访问。
Supabase Auth:提供用户注册、登录、Session 管理。CMS 后台需要区分普通访客和内容管理员,Auth 是权限控制的基础。
Supabase Storage:存储图片、视频、附件等二进制文件。Storage 通过 Bucket(存储桶)组织文件,可以设置公开或私有访问权限。
这三个服务之间是联动的。例如:上传图片时,前端需要先取得登录用户的 access_token,上传到 Storage;之后把 Storage 返回的文件路径写入 Post 表的 cover_image 字段。整个过程涉及认证、存储、数据库三部分,缺一不可。
4. 完整实战:搭建 NextBlock CMS 并接入 Supabase
下面我们实际操作一遍,从克隆项目到本地运行,再完成基本的配置和内容发布。由于 NextBlock CMS 可能持续迭代,示例中的命令和文件路径请以仓库 README 为准。这里主要演示完整的配置思路。
4.1 克隆项目并安装依赖
首先拉取项目代码:
建议使用 pnpm 安装依赖,速度更快,对 monorepo 支持也更好:
安装完成后,查看项目结构。一般情况下,项目会包含以下关键目录:
如果你看到的目录略有不同,不用紧张,重点理解配置文件和服务端工具的用途即可。
4.2 配置环境变量
项目根目录下创建 .env.local 文件。参考 .env.example 的变量名,最核心的内容包括:
变量说明:
NEXT_PUBLIC_SUPABASE_URL:浏览器端可以访问的 Supabase API 地址。本地用 CLI 启动时,地址是http://localhost:54321;线上就是你创建项目的 URL,格式类似https://xxxx.supabase.co。NEXT_PUBLIC_SUPABASE_ANON_KEY:匿名 key,用于客户端初始化 Supabase SDK。SUPABASE_SERVICE_ROLE_KEY:服务端 key,拥有最高权限,可以绕过 RLS(Row Level Security),只允许在服务端使用,绝不能暴露到浏览器端。NEXT_PUBLIC_SITE_URL:站点的对外地址,用于生成绝对路径链接。
这里的核心安全原则是:以 NEXT_PUBLIC_ 开头的变量在浏览器可见,其他变量只在服务端可见。任何含有敏感权限的 key 都不能加 NEXT_PUBLIC_ 前缀。
4.3 创建数据库表结构
如果你使用 Supabase CLI,可以把建表语句放到 supabase/migrations/ 目录下。以下是一个最小化的 Post 建表 SQL 示例,演示了核心字段和 RLS 策略配置。
先创建一个迁移文件:
写入内容:
这段 SQL 有几点需要解释:
- RLS 必须开启。PostgREST 会自动遵守行级安全策略。如果表没有开启 RLS,Supabase 的 anon key 可能直接读到你不想公开的数据。
- 读策略和写策略分离。访客只能读取
published状态的文章,登录用户只能写入,但还不能修改他人的内容。如果要精细化权限,可以在 policy 中加入author_id = auth.uid()条件。 - 触发器自动更新 updated_at。这是非常实用的工程细节,避免每次更新都要在业务代码里手动修改时间。
本地应用迁移:
如果是远程项目,可以执行:
注意,数据库的 DDL 操作建议先在本地或测试环境执行并备份,不要直接在线上生产库执行没有验证过的 SQL。
4.4 创建 Storage Bucket
图片文件需要存储到 Supabase Storage。先在本地或云端创建一个公开的 images bucket。
用 Supabase Dashboard 操作:进入 Storage → New bucket → 输入 images → 选择 Public。
或者用 SQL 创建:
创建 bucket 之后,还需要配置 Storage 的访问策略。一般来说,图片场景希望所有人可读,但只有登录用户可上传。可以通过 Storage 的 Policy 配置实现。
下面是在 Supabase Dashboard 中建议配置的 RLS 策略:
- 公开读:允许所有用户读取
imagesbucket。 - 登录用户可上传:仅
authenticated角色可执行 insert 操作。 - 登录用户可删除自己的文件:上传路径中包含用户 ID,删除时校验
storage.foldername(name)的第一个字段等于auth.uid()。
最安全的实践是:对上传请求做文件类型校验,只允许图片格式;同时限制文件大小。生产环境中还可以在服务端用 service_role key 校验文件类型后再更新记录。
4.5 初始化 Supabase 客户端
Next.js 中需要区分服务端和客户端两个 Supabase 实例。
服务端客户端位于 lib/supabase/server.ts:
客户端客户端位于 lib/supabase/client.ts:
使用 @supabase/ssr 而不是 @supabase/supabase-js 直接创建客户端,是因为服务端组件需要读取和设置 cookie,通过 Next.js 的 cookie 管理机制来维护 session。
4.6 查询文章列表
在服务端组件中查询已发布的文章列表:
查询单个文章:
这里 select("*") 是为了拿到完整的 content JSON 字段,用于后面的区块渲染。
4.7 渲染 Block 内容
拿到文章的 content 字段后,需要在页面中渲染各个区块。
app/posts/[slug]/page.tsx:
BlockRenderer 组件负责根据 type 分发到对应的渲染组件。
这里把 blocks 的类型定义成 any[] 只是为了快速演示,真实项目中建议为 JSON 结构编写 TypeScript 类型和运行时校验,避免后端数据结构变化导致页面崩溃。
对应区块组件示例:
这样一个最简单的 Block 渲染链路就通了。后续要新增图片块、代码块之外的自定义内容类型,只需要在 blockComponents 注册一个组件,然后在 content.blocks 数组中添加对应 JSON 即可。
4.8 通过 Server Action 创建文章
CMS 的核心能力之一是内容创建。在 Next.js 中,可以通过 Server Action 直接操作数据库。
app/admin/actions.ts:
这个例子没有校验 slug 是否重复,也没有处理图片上传。真实项目中,你需要在 Server Action 里完成:
- 校验表单字段是否合法。
- 校验当前用户是否有权限创建文章。
- 检查 slug 是否冲突。
- 处理图片文件,上传到 Supabase Storage。
- 使用事务或回调确保数据一致性。
Server Action 的方式降低了前后端联调成本,但要注意:服务端代码不要盲目信任用户输入,所有字段都需要做服务端校验,尤其是 content 字段的 JSON 内容。
4.9 启动本地开发服务器
所有配置完成后,启动本地开发环境:
浏览器访问 http://localhost:3000,能看到首页文章列表。进入 http://localhost:3000/admin,可以管理文章。
如果页面显示空白或报错,需要检查:
- 本地 Supabase 服务是否还在运行。
.env.local里的 key 是否匹配本地输出。- 数据库迁移是否成功。
- RLS 策略是否阻止了匿名读取。
下面我们专门用一节来整理常见问题。
5. 常见问题与排查思路
在部署和二次开发 NextBlock CMS 时,最容易遇到以下几类问题。
5.1 RLS 导致文章读取不到
问题现象:首页或文章详情页显示内容为空,控制台没有任何 Next.js 服务端错误,但数据库里明明有文章数据。
常见原因:Post 表的 RLS 策略没有配置,或者配置错误。Supabase 开启了 RLS 的表,如果没有任何 policy,PostgREST 会拒绝所有请求,包括 anon key 的读请求。
解决思路:
- 进入 Supabase Dashboard → Authentication → Policies,检查
posts表的 policy。 - 确认存在
for select using (status = 'published')的策略。 - 如果策略没有生效,可以在 SQL Editor 中执行
drop policy if exists后重建。
预防方案:在本地用 supabase db push 推送迁移时,先用 supabase db reset 重建数据库,通过 SQL 脚本保证 RLS 策略始终存在。
5.2 Supabase Storage 上传返回 403
问题现象:登录用户上传图片时,Supabase Storage 返回 403 Forbidden。
常见原因:Storage bucket 的插入策略没有配置,或上传请求没有附带认证 header。
解决思路:
- 检查 Storage → Policies,确认存在允许
authenticated角色 insert 的策略。 - 检查上传代码是否通过 Supabase SDK 的
storage.from("images").upload()方法调用,SDK 会自动附带认证信息。 - 如果使用独立 fetch 请求,需要手动在 header 中加入
Authorization: Bearer <access_token>。
预防方案:优先使用 Supabase SDK 操作 Storage,不要自己拼 API 请求。
5.3 Next.js 生产环境无法读取环境变量
问题现象:本地开发正常,但部署到 Vercel 后,动态功能失效,Supabase 连接失败。
常见原因:部署平台的环境变量没有配置,或者只配置了本地 .env.local,没有同步到云端。
解决思路:
在 Vercel 项目设置 → Environment Variables 中,逐一添加:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEYNEXT_PUBLIC_SITE_URL
注意 SUPABASE_SERVICE_ROLE_KEY 不要以 NEXT_PUBLIC_ 开头,否则会被打包到浏览器端代码,造成密钥泄露。
预防方案:在 .env.example 中登记所有环境变量,并在 README 中说明哪些是公开变量、哪些是服务端私有变量。
5.4 文章列表返回后卡片封面图无法显示
问题现象:文章列表渲染正常,但封面图显示裂图,控制台 404。
常见原因:图片路径存储的是相对路径,而页面渲染时未拼接 Supabase Storage 的完整 URL。
解决思路:
在服务端查询时直接拼接完整路径:
或者在渲染组件中用工具函数统一拼接,避免每个页面各写一遍。
预防方案:在数据库表中存储相对路径,渲染层统一通过图片 URL 工具函数生成带 bucket 的完整 URL。这样迁移 bucket 名称时只需要改一个地方。
5.5 内容区块数据是 JSON,TypeScript 类型不安全
问题现象:后端返回的 content.blocks 是 any 类型,页面组件里访问 block.props.text 时可能拿到 undefined,页面渲染异常。
常见原因:JSON 字段没有运行时校验,也没有 TypeScript 类型守卫。
解决思路:
为每个 Block 类型定义类型,并写一个解析函数:
然后在页面组件中使用:
预防方案:编写内容编辑器的导出逻辑时,确保 blocks 数组中的每一项都符合预定义结构;在后端写入前做 JSON Schema 校验会更稳妥。
5.6 Server Action 中无法设置 cookie 导致 Auth 报错
问题现象:在 Server Action 中调用 supabase.auth.signInWithPassword() 时,报错提示 cookie 写入失败。
常见原因:@supabase/ssr 要求 cookie 的 set 操作发生在 Server Action 或 Route Handler 中,而不是在 Server Component 渲染期间。
解决思路:登录操作不要在服务端组件的函数里直接调用,而是在 Server Action 或 Route Handler 中执行。代码位置可以参考 app/auth/callback/route.ts,并让前端通过 <form action={login}> 提交。
预防方案:凡是涉及 Supabase Auth 的 cookie 操作,都通过 Server Action 或 Route Handler 封装,避免在 Server Component 中直接调用认证方法。
5.7 发布后文章不生效,页面还是旧内容
问题现象:在后台改了文章内容,前台页面没有反映最新修改。
常见原因:Next.js 的服务端渲染或静态生成缓存了页面内容,没有触发重新验证。
解决思路:
- 如果使用 Server Component 动态渲染(默认),修改后重新访问通常能拿到新数据。
- 如果页面配置了
export const dynamic = "force-static"或generateStaticParams,需要手动调用revalidatePath或revalidateTag。
在创建/更新文章的 Server Action 里加一行:
预防方案:内容更新操作集中放在 Server Action 中,并在每次操作后统一 revalidatePath,保证页面与数据库内容一致。
6. 最佳实践与工程建议
如果你决定在真实项目中使用 NextBlock CMS,或者参考它的架构搭建自己的 CMS,以下工程经验值得借鉴。
6.1 内容模型设计建议
不要把所有内容都塞进一个超级宽表。虽然 jsonb 灵活,但过度使用会带来两个问题:
- 查询效率下降。
jsonb字段无法像普通列那样使用常规索引,复杂查询会变慢。 - 数据结构失控。没有约束的 JSON 内容会导致不同文章之间风格迥异,渲染组件难以统一。
推荐做法:
- 基础字段(标题、slug、摘要、发布时间、状态)用独立列存储,方便排序、过滤、索引。
- 正文内容放在
jsonb字段中,由 Block 结构统一管理。 - 需要索引的 JSON 字段,使用 GIN 索引,但不要对整列建立表达式索引,除非查询频率很高。
- 新增内容类型时,尽量用区块组合,而不是无脑增加表。
6.2 数据库安全边界
RLS 是 Supabase 安全模型的核心,必须理解并正确使用。
第一,所有表默认开启 RLS。只要数据不打算完全公开,就开启 RLS。
第二,写策略要清楚区分「谁可以插入」「谁可以更新」「谁可以删除」。不要简单用一个 auth.role() = 'authenticated' 放开所有登录用户的写权限。至少应该限制为用户只能修改自己的内容:
第三,service_role key 不要出现在浏览器端代码里。服务端代码中调用 createClient 时,默认依然使用 anon key 加用户 session。只有在特殊场景(例如后台批量导入、定时任务)才用 service_role key。
6.3 Storage 使用建议
图片上传是 CMS 最常见的功能,使用 Supabase Storage 时有几个实践要点:
文件命名规则。不要把用户上传的文件名直接存为对象名,推荐使用唯一 ID 加扩展名:
限制文件类型和大小。Storage 本身有配置项,但建议在上传前校验一次,在服务端再校验一次:
使用图片优化。Next.js 的 next/image 组件可以自动做图片尺寸优化和格式转换。需要把 Supabase Storage 的域名添加到 next.config.js 的 images.remotePatterns 中:
私有文件的访问。如果图片是付费内容或内部资料,使用公开 bucket 不合适。可以用 Supabase Storage 的 Signed URL,设置有效期后访问:
签名 URL 有过期时间,适合临时分享,但注意也会增加每次请求的开销。
6.4 性能优化
CMS 页面通常是读多写少,性能优化的重点在读取路径。
第一,服务端组件中直接查询数据库,不要在前面额外加一层 REST API 包装。Next.js 服务端组件会在服务器上执行查询,数据直接进入渲染流程,减少一次网络请求。
第二,合理使用 Next.js 的缓存能力。对不经常变化的页面使用静态生成或增量静态再生成;对频繁更新的内容页使用动态渲染加 revalidatePath。
第三,控制单次查询的数据量。列表页不需要读取完整的 content JSON,只读取标题、摘要、封面图等轻量字段。
第四,为高频查询字段建立索引。slug 必须有唯一索引,status + published_at 组合使用频率高,可以建复合索引。
6.5 内容备份与恢复
数据库是 CMS 的核心资产,必须建立备份机制。
Supabase 本身提供连续的备份服务,但本地开发和自托管场景需要手动处理。推荐每天用 pg_dump 导出一次数据库结构加数据:
恢复时:
Storage 中的图片也需要备份。可以写一个定时任务,把 Storage 文件同步到本地或者云存储的另一个 bucket。
6.6 内容审核与发布流程
内容管理不仅仅是技术问题,还要考虑发布流程。
建议在数据模型中加入 status 字段,支持 draft、reviewing、published 三种状态:
- 编辑创建内容后默认为
draft。 - 内容审核通过后改为
reviewing。 - 最终发布时改为
published。
页面上只展示 published 状态的内容,后台可以预览 reviewing 状态。这样即使多个运营人员同时协作,也不会出现「改了一半的内容被用户看到」的尴尬情况。
同时,在 Server Action 中记录「最后修改人」和「修改时间」,为后续的内容审计和回滚提供依据。
7. 总结与学习路线
这篇文章围绕 NextBlock CMS 做了一次比较完整的拆解,从项目定位、核心架构到数据库设计、区块渲染、Server Action 发布,再到 Supabase Storage 接入和常见排错,基本覆盖了一个内容管理系统的核心链路。
读完本文,你应该掌握以下关键点:
- NextBlock CMS 是用 Next.js 16 + Supabase 组合而成的开源全栈 CMS,核心优势是内容区块化、数据模型代码化、部署轻量化。
- 数据模型集中在
posts表,正文以jsonb的blocks数组存储,通过type字段映射到对应的 React 渲染组件。 - Supabase 在系统中承担数据库、认证、存储三个角色,RLS 是安全体系的关键,任何表开启 RLS 后都必须正确配置 policy。
- 图片上传走 Supabase Storage,公开访问时用公开 bucket,私有内容用签名 URL。
- 内容更新后需要通过
revalidatePath让 Next.js 重新验证缓存,否则页面不会显示最新内容。
接下来你可以继续深入的方向:
- 编辑器集成。目前内容 JSON 是手动构造的。可以接入 TipTap、Plate 或亲自实现一个支持 Block 编辑的富文本编辑器,把编辑器的输出直接对接
content.blocks。 - 权限细化。当前写权限是登录用户即可。可以进一步引入角色表,区分管理员、编辑、投稿者,并在 RLS 策略中做出更细粒度的控制。
- SEO 优化。为文章页补充 Open Graph 标签、结构化数据(如 BlogPosting schema)、站点地图生成,利用 Next.js 的 Metadata API 统一管理。
- 内容工作流。实现草稿、审核、发布的完整状态机,支持定时发布。
- 多语言支持。Post 表增加语言字段和翻译关联,渲染层根据站点语言选择对应内容。
- 自托管部署。如果你不想依赖云服务,可以使用 Supabase 自托管版本或直接用 Postgres 加 open-source 的 GoTrue、Storage 服务,把所有服务部署到自己的服务器。
最后提醒一句:CMS 类项目的数据是核心资产。无论使用 NextBlock CMS 还是自己从零搭建,都要保证数据库有备份策略,Storage 中的文件有冗余,环境变量中的密钥严格保密。在这个基础上,再去扩展功能才没有后顾之忧。
如果这篇文章对你有帮助,可以收藏备用;也可以在本地把项目跑起来,动手改一个自定义 Block,体验一下「内容即组件」的开发方式。实践一次之后,你会对 Next.js 全栈开发有更直观的理解。