Zlog
首页时间轴关于
Zlog

一个探索技术、编程和构建 Web 的个人空间。

© 2026 Zlog

导航

首页时间轴关于

链接

首页/Zlog 部署指南

Zlog 部署指南

从系统架构到环境变量,一文讲清 Zlog 博客的部署方式:Next.js 静态导出、Turso 数据库、GitHub 与 Vercel 的配置清单。

Z

Zephyr110

2026年8月3日·13 分钟
|
zlogdeploymentverceltursonextjs

项目概览

Zlog 是一个「静态优先」的个人博客系统,管理后台与前台同仓库:前台在构建时静态导出(SSG),后台在开发模式下提供完整的管理能力。核心技术选型:

层选型说明
框架Next.js 16(App Router + Turbopack)前台静态导出,后台 CSR
样式Tailwind CSS v4 + shadcn/ui全站组件化
数据库Turso(libSQL)+ @libsql/client零运维云数据库,走 HTTP
认证bcrypt + jose JWT密码哈希 + 签名令牌 + 失败锁定
评论Giscus(GitHub Discussions)免自建后端
图片GitHub 仓库 + CDN上传走 GitHub Contents API
部署Vercel(纯静态托管)构建时注入环境变量

整体架构

Zlog 是一个 pnpm workspace monorepo:

text
zlog/
├── apps/
│   └── web/                 # Next.js 应用(前台页面 + /admin 后台)
├── packages/
│   ├── core/                # 共享类型与工具(Post 类型、safeSlug 等)
│   ├── auth/                # JWT 签发/校验、登录尝试与锁定状态
│   └── database/            # Turso 客户端、表结构、数据访问层
└── scripts/                 # create-admin 等辅助脚本

生产环境(静态导出):浏览器 → Vercel 托管的纯静态文件。所有文章在构建时从数据库一次性读取,生成 HTML:

开发模式:next dev 同时提供前台页面和 /api/* 路由,/admin 后台在浏览器中调用这些 API 完成登录、写文章、传图等操作:

前台与后台共享同一套 packages/database 数据访问层,构建时(SSG)与开发时(API)读到的永远是同一份数据。

前端架构

前台是标准的 Next.js App Router 静态导出:

  • 构建:pnpm export(等价于 NEXT_EXPORT=true next build),output: 'export' 把所有页面预渲染成纯静态 HTML,输出到 out/
  • 页面:首页文章流、/posts/[slug] 详情、标签、时间线、关于页等,全部通过 generateStaticParams 从数据库生成
  • 文章渲染:MDX(GFM + rehype-pretty-code 高亮),前台与后台编辑器的预览使用同一套组件映射,所见即所得
  • 后台:/admin/* 全部是客户端组件(CSR),静态导出后仍可访问页面本身,但增删改依赖 API,因此管理操作在本地开发模式完成
  • Monorepo 注意点:next.config.ts 里通过 transpilePackages 编译 @zlog/* 工作区包,并显式设置 turbopack.root 指向 workspace 根目录,避免 Turbopack 向上误判根目录(详见文末踩坑记录)

后端与 API

后端能力全部集中在 API Routes(仅开发/自托管模式可用):

路由职责
/api/auth/login /logout /me登录、登出、当前用户
/api/auth/change-password修改密码
/api/auth/recovery /reset忘记密码(恢复码重置)
/api/posts文章列表 / 创建 / 更新 / 删除
/api/media /api/upload媒体列表 / 上传(写入 GitHub 仓库)
/api/site-settings站点名称、Logo、导航等设置

静态导出后 /api/* 不再存在——这是设计使然:生产环境没有服务端,管理操作统一在本地开发环境完成,改动直接落到数据库。

数据库

数据库是 Turso(libSQL):SQLite 的云托管版本,零运维、按量付费,通过 HTTPS 访问(@libsql/client 底层是 HTTP pipeline 协议),构建和开发环境都能直连。

共 5 张表:

sql
-- 文章(核心表)
CREATE TABLE posts (
  id            INTEGER PRIMARY KEY AUTOINCREMENT,
  slug          TEXT UNIQUE NOT NULL,      -- URL 标识
  title         TEXT NOT NULL,
  date          TEXT NOT NULL,             -- 发布日期
  updated       TEXT,                      -- 最后更新
  tags          TEXT NOT NULL DEFAULT '[]',-- JSON 数组
  description   TEXT NOT NULL,
  cover         TEXT,                      -- 封面图路径
  draft         INTEGER NOT NULL DEFAULT 0,-- 0=已发布 1=草稿
  content       TEXT NOT NULL,             -- MDX 正文
  word_count    INTEGER NOT NULL DEFAULT 0,-- 构建时用于阅读时长
  reading_time  INTEGER NOT NULL DEFAULT 0,
  created_at / updated_at  TEXT
);
CREATE INDEX idx_posts_date ON posts(date DESC);
CREATE INDEX idx_posts_draft ON posts(draft);
 
-- 媒体(上传记录,图片本身存在 GitHub 仓库)
CREATE TABLE media (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  filename TEXT UNIQUE NOT NULL,
  content_type TEXT NOT NULL,
  size INTEGER NOT NULL,
  data BLOB NOT NULL,          -- 本地缓冲
  github_sha TEXT,             -- GitHub 上的 blob SHA
  created_at TEXT NOT NULL
);
 
-- 站点设置(名称、Logo、导航等,admin 可改)
CREATE TABLE site_settings ( ... );
 
-- 管理员账号
CREATE TABLE users ( ... );
 
-- 登录失败锁定(节流)
CREATE TABLE auth_lockout ( ... );

认证与安全

  • 登录:ADMIN_USERNAME + ADMIN_PASSWORD_HASH(bcrypt 哈希,由 scripts/create-admin.mjs 生成)比对成功后,用 SESSION_SECRET 签发 JWT(jose 库),前端持有 token 调用受保护的 API
  • 锁定:连续失败会写入 auth_lockout 表,按失败次数指数级递增锁定时长,防止爆破
  • 密钥分级:数据库地址、管理密码、JWT 密钥、GitHub token 全部只存在于服务端环境(Vercel / 本地 .env.local),浏览器只拿得到 NEXT_PUBLIC_* 变量

媒体存储

图片不走数据库大字段,而是:上传时通过 GitHub Contents API 写入独立的图片仓库(默认 zephyr110/blog-img),media 表记录文件名与 GitHub blob SHA,页面 URL 由 BLOG_IMG_CDN_BASE 拼接(可换任何 CDN)。这样数据库小、加载快、GitHub 天然带版本历史。

GitHub 配置

  • 主仓库:zephyr110/zlog,main 分支为发布分支,改动合入后触发 CI 与 Vercel 部署
  • 图片仓库:zephyr110/blog-img(可选配置 BLOG_IMG_REPO),需要一个 fine-grained token:权限只勾选该仓库的 Contents: Read and write,写入 BLOG_IMG_GITHUB_TOKEN
  • Giscus:在 giscus.app 完成 GitHub App 授权,选择仓库并生成四项配置(repo / repoId / category / categoryId),分别填入 NEXT_PUBLIC_GISCUS_*
  • 仓库设置里开启 Discussions(Giscus 的评论载体)

Vercel 配置

  1. 导入仓库:New Project → 选择 zlog 仓库,Framework Preset 选 Next.js(自动识别)
  2. Monorepo:Root Directory 设为 apps/web;Build Command 使用 pnpm export(等价 NEXT_EXPORT=true next build,产物在 out/,Next.js preset 会自动识别输出目录);Node.js 版本 24
  3. 环境变量:把下方清单全部粘贴到 Environment Variables(Production 与 Preview 一致)
  4. 域名:默认 https://zephyr110.vercel.app,可在 Domains 添加自定义域名
  5. 部署后每次 push 到 main 自动重新构建:构建时从 Turso 拉取最新文章,静态站点随之更新
“

注意:修改 Vercel 的 Project Name 会影响 OpenID Connect Token claims 等配置引用;若改了名称,请同步检查环境变量(尤其是 NEXT_PUBLIC_GISCUS_REPO),并重新部署一次。

环境变量清单

客户端(NEXT_PUBLIC_*,构建时注入,会暴露给浏览器):

变量用途示例
NEXT_PUBLIC_SITE_URL站点根 URL(OG 图、feed、sitemap 拼接)https://zephyr110.vercel.app
NEXT_PUBLIC_OG_IMAGE默认分享卡片图/images/og-default.jpg
NEXT_PUBLIC_GISCUS_REPO评论仓库zephyr110/zlog
NEXT_PUBLIC_GISCUS_REPO_IDGiscus repo IDR_kgDO…
NEXT_PUBLIC_GISCUS_CATEGORY评论分类Announcements
NEXT_PUBLIC_GISCUS_CATEGORY_ID分类 IDDIC_kwDO…

服务端(仅构建/运行环境,禁止泄露):

变量用途示例
TURSO_DATABASE_URLTurso 数据库地址libsql://zlog-xxx.turso.io
TURSO_AUTH_TOKEN数据库访问令牌eyJhbGciOi…
ADMIN_USERNAME管理员账号admin
ADMIN_PASSWORD_HASHbcrypt 密码哈希$2b$10$…
SESSION_SECRETJWT 签名密钥(长随机串)openssl rand -hex 32 生成
BLOG_IMG_GITHUB_TOKEN图片仓库 fine-grained tokengithub_pat_…
BLOG_IMG_REPO图片仓库(可选)zephyr110/blog-img
BLOG_IMG_BRANCH图片分支(可选)main
BLOG_IMG_CDN_BASE图片 CDN 前缀(可选)https://cdn.jsdelivr.net/gh/…
BLOG_IMG_QUALITY图片压缩质量(可选)80

本地开发时全部写入 apps/web/.env.local(已 gitignore);Vercel 部署时在项目 Settings → Environment Variables 里配置。

部署与发布流程

要点:

  • 发布即入库:草稿 draft=1 不出现在前台,改为发布(draft=0)后,下次构建就会包含它
  • 静态导出的特性:文章变更必须触发一次重新构建才会上线(没有 SSR 实时性);推送一个空提交或 Vercel 手动 Redeploy 均可
  • 回滚:把文章改回草稿再构建,文章即从线上消失

踩坑记录

  • Turbopack root 误判:Next.js 会自动向上查找 lockfile 推断 workspace root;若机器上存在多余的 package-lock.json(比如全局 npm 残留),root 会被推断到错误位置,导致 @zlog/* 无法解析。显式在 next.config.ts 设置 turbopack.root 到 workspace 根目录解决
  • Giscus 报 "not installed on this repository":NEXT_PUBLIC_GISCUS_REPO 必须与 GitHub 上的实际仓库名一致;改过仓库名或 Vercel 项目名后最容易忘更新这个变量,改完记得 Redeploy
  • 改 Vercel Project Name 有连锁影响:会提示影响 OIDC token claims,且环境变量面板不变——重命名后应检查一遍所有 NEXT_PUBLIC_* 并重新部署
  • admin 在静态站上不可写:生产环境没有 /api,管理操作请在本地 pnpm dev 完成(这是架构的取舍,不是缺陷)

标签

zlogdeploymentverceltursonextjs

评论

← 返回文章列表