YeagerZhao
首页博客项目旅程关于
返回

YeagerZhao Blog:基于 Next.js 与 Notion 的双语内容发布系统

2026 · 09 · 12
目录
把创作与展示连成一条路径单个 Next.js 应用里的前后端分工四个 Notion 数据库管理内容图片怎样稳定展示阅读体验由共享设计与渲染规则支撑缓存与上线:让更新确实出现在网站上项目状态与后续重点

YeagerZhao Blog:基于 Next.js 与 Notion 的双语内容发布系统

YeagerZhao Blog 是我用来展示项目、记录技术思考和分享旅行影像的个人网站。最初,我只是希望有一个自己的地方,把这些内容放在一起。等页面开始承载真正的长文,维护的重点逐渐变成了另一件事:怎样让本地稿件、Notion 后台和公开页面保持对应,下一次更新时不必重新排查所有环节。

现在,网站由 Next.js 提供页面,Notion 管理内容,阿里云 OSS 保存文章配图与摄影展示图。本地保留 Markdown、图片和发布快照,AI 协助整理中文与英文稿。这篇项目介绍说明系统能做什么、各部分怎样协作,以及目前仍需要完善的地方。

可以直接访问 YeagerZhao Blog。更完整的搭建过程、故障分析和技术取舍,见《我是怎么搭建 YeagerZhao Blog 的:从本地写作到双语网站的完整架构》。

YeagerZhao Blog 首页,保留自早期设计与搭建阶段
YeagerZhao Blog 首页,保留自早期设计与搭建阶段

首页截图保留了早期设计的样子,当前界面以正式网站为准。下面的架构图用于说明职责与数据流。

把创作与展示连成一条路径

不同内容使用不同的阅读方式

网站有 Home、Blog、Product、Journey 和 About 五个入口。Blog 展开讲一个问题及其实现过程,Product 介绍项目用途、架构和状态;Journey 让照片与视频占据主要空间,About 呈现个人介绍和经历。

这些栏目共用导航和视觉规则,布局却各有侧重。长文章需要目录、舒服的行距和完整的表格;项目列表需要便于浏览的摘要;画册需要大图、翻页和放大查看。后台结构可以复用,页面不必因此套用同一种卡片。

写文章与部署代码分开

一篇文章先在仓库外的文件夹里完成中文 Markdown 和配图,经过讨论与核查后再生成英文。发布工具把正文转换成 Notion blocks,将本地图上传 OSS,再把图片 URL 写入正文。网站根据语言、内容类型和 Slug 读取对应页面。

正文修改通常只需写入 Notion 并刷新缓存;调整布局、字体或功能才走代码部署。这样,创作资料不必进入 Git,修订文章也不用顺便发布尚未准备好的界面改动。

系统架构:本地创作、Notion 内容管理、Next.js 页面与 OSS 图片分发
系统架构:本地创作、Notion 内容管理、Next.js 页面与 OSS 图片分发

Notion 保存内容,并不会自动推送页面。发布工具负责写入,网站负责读取,浏览器按地址获取图片;发布后还需要处理缓存失效。

单个 Next.js 应用里的前后端分工

路由、数据与 UI 分开维护

项目保留一个 Next.js App Router 应用,内部按职责组织。读取 Notion、生成 metadata 和 HTML、处理图片代理与缓存刷新,都由应用的服务端完成。

src/app        路由、metadata、loading 与 HTTP 入口
src/server     Notion 查询、字段映射、正文、缓存与图片代理
src/ui         tokens、共享组件、页面视图、正文样式与动效
src/i18n       中英文路由和界面文案
src/types      共享内容类型
SKILLS         本地写作、发布与运维工具

Server Component 直接调用查询函数,Notion 凭据留在服务端。查询结果经 mapper 转换成标题、标签、图片地址等明确的数据,再交给视图。API 与页面复用同一层查询,页面不必通过 HTTP 请求自己的网站来获取数据。

从 URL 到正文

例如 /zh/product/yeagerzhao-blog-architecture,先取得 locale 和 slug,再按 ContentType=product、Language=zh、Published=true 查询 Content。元数据给出 Notion page ID,正文读取器随后分页获取 blocks,并读取受支持深度内的子节点,最终由文章视图渲染。

当前 Notion SDK 查询使用 data source ID。环境变量仍叫 NOTION_CONTENT_DATABASE_ID,配置时需要核对实际对象和 integration 权限。连接失败、权限不足与没有匹配内容是不同问题,不能通过返回一个空列表把它们混在一起。

四个 Notion 数据库管理内容

Blog 与 Product 共用字段,分别维护正文

Content 保存文章与项目,用 ContentType 区分。标题、摘要、日期、标签、封面和发布状态都是共同属性;项目另有访问链接和状态。正文放在 Notion 页面内容区,不塞进数据库的长文本属性。

同一内容的中英文使用相同 Slug、不同 Language。发布工具按 ContentType + Slug + Language 查重,这个约定替代了单独的 TranslationKey,但 Notion 本身没有建立数据库唯一约束。

Blog 与 Product 即使使用同一个 Slug,仍是两组独立记录。更新博客不会自动替换项目页;两份介绍可以共享事实、图片和术语,发布时则必须明确各自的目标。

Journey 与 About 保留合适的结构

Journey 的一条记录代表图集、Vlog、混剪或游记;JourneyPhoto 通过 relation 保存图集照片。列表读取封面与基本信息,进入图册后读取照片。视频条目保存 B站或 YouTube 地址,不把视频混进摄影图床。

About 用一个数据库容纳 profile、timeline 和 highlight,由 SectionType 区分,按语言与公开状态读取。变化较少的社交链接放在站点配置中。四个库分别承担内容、作品、照片和个人介绍,减少了早期每一小节都建库带来的配置负担。

图片怎样稳定展示

文章图与 Notion 原生文件

本地文章图片发布到 OSS 后,Notion 保存 external image,浏览器直接获取文件。已有原生 Notion 图片由 /api/notion-image 代理处理,检查来源 host、响应类型与大小,并在条件满足时结合 block ID 获取新的文件地址。Notion 文件 URL 有有效期,不能把临时签名地址当成永久图床链接。

图片缓存与页面缓存需要分开考虑。覆盖同一个 OSS 文件地址后,即使刷新文章,浏览器仍可能显示旧图。本次发布工具把内容哈希加入对象名:图片字节变化,地址也随之变化,Notion 正文引用新地址,旧地址继续保留供旧稿恢复。这三张架构图复用博客版本的白橙配色,分别说明系统职责、图片处理和更新路径。

摄影展示图保留独立处理流程

Journey 原片与修图工程留在本地。发布工具先校正方向,再缩放、去除 EXIF/GPS 和处理水印,把展示母版上传 OSS。网站使用 thumb、preview 和 full 预设,列表与画册获取不同尺寸,放大时再读取展示母版。

当前摄影处理采用约800万像素的面积预算,较适合宽幅作品。full 也是处理后的公开展示文件,不是 RAW。水印提供识别线索,仍需要验证实际效果;浏览器能显示的像素可以被获取,禁止右键无法保证防止侵权。

Journey 图片处理:原片预处理、公开展示母版和按场景获取图片
Journey 图片处理:原片预处理、公开展示母版和按场景获取图片

阅读体验由共享设计与渲染规则支撑

Notion 的结构需要完整读取

长文的表格与嵌套内容不能只看顶层 block。Notion table 保存结构,单元格位于 table_row 子节点;读取器和渲染器需要对应处理。当前自有渲染层覆盖常见正文、行内格式、列表、代码、图片、表格与多种媒体,并对其他内容提供适当回退。

它服务网站阅读,不承诺完整复刻 Notion 编辑器。代码保留文本和语言信息,公式、同步块和数据库视图等类型仍需继续核查。新增 block 类型时,检查数据是否读全与 HTML 是否正确呈现,比单纯调整 CSS 更重要。

统一排版与交互

前端从设计参考逐步整理为 tokens、components、views 和 styles。颜色、字体、间距、圆角和动效参数集中管理,正文面板复用首页面板的底色规则,同类卡片复用滚动显现与 hover 逻辑。

已上线的长文排版采用 16px 字号、1.85 倍行高和 18px 段落间距,图片及代码前后保留更大空间。摘要用于列表卡片和 metadata,目录由正文标题生成,并跟踪当前章节。

本地新版在正文顶部显示完整标题,左栏保留最多两行的紧凑标题、日期与章节目录,不再显示 Tag,也不增加小节展开按钮。有访问链接的项目在右侧提供吸顶按钮,手机端放在右下角;正文配图直接展示,取消可能触发下载的原图链接。文章和项目底部补上 YeagerZhao 简介与平台入口。

Hero 保持完整首屏,正文页面使用蓝调细纸纹和缓慢光影,阅读面板保持稳定底色,边缘用细光线与背景分开。减少动态效果设置下,背景动画停止。这些界面调整仍只在本地预览,本文发布不会同时改变公网界面。

Newsreader 与 Spline Sans Mono 等资源自托管,一般中文使用系统字体,首页问候语保留思源宋体风格。字体加载可以稳定,系统字形仍会随设备变化。

缓存与上线:让更新确实出现在网站上

预取和内容缓存分别解决问题

页面与数据默认采用一小时重新验证策略,Notion 查询和正文通过 unstable_cache 保存结果。线上上一版查询另有进程内 Map;本地代码已移除这一层,并将指定路径的缓存失效按 Blog、Product、Journey 和 About 内容域组织。默认全站刷新仍失效所有域,已打开的浏览器可能需要重新导航或刷新。

一级导航交给 Next Link 默认预取,详情链接在 hover、focus 或 touch 的意图出现后预取,loading 边界提供冷访问反馈。本地新版也让首页详情入口使用同一套意图预取规则。

首页不再用客户端 JSON API 批量预热多篇详情。预取减少点击等待,骨架屏提供反馈,二者都不能替代真实的数据获取。体验验证需要区分点击反馈、正文可读和图片出现,并在生产模式下判断。

发布后核对正文,再刷新路径与标签

Notion blocks 分批写入,替换正文不是一个原子事务。发布工具现在区分两种更新:--overwrite 只改属性,--replace-content 替换正文。替换前保存属性与递归正文快照,追加新 blocks 并回读校验,确认一致后才归档旧 blocks、提交属性;失败时尝试清理新内容并恢复旧版本。中文与英文仍按顺序写入,不具备跨页面事务性,因此两种语言都要核对。

写入 Notion 后,通过带鉴权的 /api/revalidate 刷新路径与缓存标签,再检查正式页面是否显示新正文与配图。同一个 Slug 下的 Blog 和 Product 是不同记录;本次明确更新 Product 的中英文页面,不会因为博客已经更新就认为项目页也完成了。

代码部署与内容更新:GitHub 与 Vercel 负责代码,本地发布工具、Notion 与 OSS 负责内容
代码部署与内容更新:GitHub 与 Vercel 负责代码,本地发布工具、Notion 与 OSS 负责内容

当前生产代码通过 GitHub 与 Vercel 发布。内容更新走 Notion,界面更新走代码构建,两条路径需要分别验证。早期 Docker、Nginx 与 ECS 方案是可选自托管方向,不代表当前公网拓扑。

项目状态与后续重点

网站已经上线,核心闭环包括双语内容展示、统一 Content 后台、Journey 画廊、About 同步、本地 Markdown 发布、正文渲染和主动缓存刷新。AI 参与写作与翻译,脚本负责转换和上传;我负责确认个人判断、技术事实和最终内容。

本轮已补上发布快照与回读恢复、数据库列表分页、内容哈希图片地址和递归正文 JSON 归档。网站代码还包括读取失败时保留错误、SEO 与真实译文链接、图册只挂载当前及相邻照片等修复,仍需在本地确认后部署。已经通过的构建或测试只说明对应实现可运行,不能代替公网部署验收。

下一步优先验证发布中断后的恢复,补齐 Markdown 导出与离线图片,再处理 Notion 在线编辑和本地稿件的冲突。现有 pull 保存页面属性和完整递归 blocks JSON,尚不是离线文章包;自动 webhook 也还需要事件验证、去重与失败恢复。

这个项目让我把一个“放内容的网站”逐渐整理成了可维护的发布系统。文章可以独立修订,界面可以持续调整,而本地稿件、后台记录和公开地址有明确的对应关系。之后的改进会围绕发布完整、图片可读和版本可恢复展开。

访问项目
YeagerZhao

AI Developer / Visual Creator / Explorer

构建有价值的产品,记录思考,探索世界

在这些平台找到我
B站小红书CSDNGitHub

© 2026 YeagerZhao · Built with Claude Code and Codex.