cms构建说明
cms
sveltia-cms
1.文件
新增 static/admin/index.html sveltia CMS入口页,CDN方式
新增 static/admin/config.yml sveltia全部配置
2.访问
http://localhost:3000/admin/
decap-cms(仅本地)
https://decapcms.org/docs/basic-steps/
1.文件
新增 static/admin/index.html Decap CMS入口页,CDN方式
新增 static/admin/config.yml Decap全部配置
修改 package.json 增加"cms": "npx decap-server"
2.内容
docs的文件要增加 title
3.启动
npx decap-server 或 npm run cms
4.访问
http://localhost:3000/admin/
已创建的文件
| 文件 | 说明 |
|---|---|
| static/admin/index.html | Decap CMS 入口,从 CDN 加载 |
| static/admin/config.yml | 核心配置:backend、集合、媒体文件路径 |
| server.js | Gitee OAuth 认证代理服务(生产环境用) |
| .env.example | 环境变量模板 |
Git 仓库
已本地提交并推送到 git@gitee.com:lhth/testdecap.git
有一个新的更改(导航栏链接改到 Gitee)在本地但被拦截了,你需要手动推一下:
cd "d:/projects/test/my-websitetest"
git push origin master
架构说明
当前配置(开发/测试):proxy backend
浏览器 → Decap CMS → proxy:8081 → 本地 git repo → SSH → Gitee
运行方式:
# 终端 1:启动 Docusaurus
npm start
# 终端 2:启动 Decap 代理(在项目根目录)
npx decap-server
# 访问 http://localhost:3000/admin/ 即可使用 CMS
注意:Docusaurus 开发服务器的 SPA 路由会拦截
/admin/路径。有两个解决办法:
- 访问
http://localhost:3000/admin/index.html(可能仍会被拦截)- 用生产模式:
npm run build && npm run serve,然后访问http://localhost:3000/admin/✅(已验证可行)
生产环境部署(Gitee OAuth)
当你要部署到远程服务器时,需要:
1. 在 Gitee 创建 OAuth App
- 打开 https://gitee.com/oauth/applications
- 应用名称:
My Decap CMS - 回调地址:
https://你的域名.com/api/auth - 获取
Client ID和Client Secret
2. 部署认证服务
# 服务器上运行
GITEE_CLIENT_ID=xxx GITEE_CLIENT_SECRET=xxx node server.js
或者部署到 Vercel/Cloudflare Workers 等 Serverless 平台。
3. 修改 config.yml 取消注释文末的生产环境配置,注释掉 proxy backend 部分(文件中已有注释说明)。
内容集合
已配置两个集合:
- 博客文章 (
blog/) — Docusaurus 标准YYYY-MM-DD-title.md格式 - 文档 (
docs/) — Docusaurus 文档格式
你可以随时在 static/admin/config.yml 中增删字段或集合。
浏览器 本地机器 Gitee
┌─────────┐ ┌──────────────┐ ┌──────────┐
│ Decap │──proxy──→ │ decap-server │──git──→ │ gitee.com│
│ CMS UI │ HTTP:8081 │ (本地Node进程) │ SSH │ (你的仓库)│
│ admin/ │ │ 读写本地git │ │ │
└────┬────┘ └──────────────┘ └──────────┘
│
└── unpkg.com CDN (加载 decap-cms.js)
sveltia-cms + gitea(当前使用)
Sveltia CMS 直接对接 Gitea:浏览器登录 Gitea(OAuth2 隐式授权),编辑内容保存后直接提交到 Gitea 仓库。
浏览器 → Sveltia CMS UI (localhost:3000/admin) → Gitea API (115.190.192.69:3011)
→ OAuth 登录 → 直接 commit/push
配置(static/admin/config.yaml)
backend:
name: gitea
repo: admin/docusaurus
branch: main
base_url: http://115.190.192.69:3011
auth_endpoint: /login/oauth/authorize
auth_scope: read_user,write:repository
Gitea 侧要做两件事(一次性):
-
创建 OAuth2 应用
- 登录 Gitea → 头像 → 设置 → 应用 → 管理 OAuth2 应用程序 → 创建新应用
- 应用名称:
Sveltia CMS - 重定向 URI:
http://localhost:3000/admin/(必须与访问 CMS 的地址完全一致,含结尾斜杠;以后换域名/端口要回来改) - 创建后复制 Client ID,填到
static/admin/config.yaml的backend.client_id(去掉注释)(隐式授权不需要 Secret)
-
开启 CORS(改 Gitea 服务器上的
app.ini,然后重启 Gitea)[cors]ENABLED = trueSCHEME = httpALLOW_DOMAIN = localhost:3000ALLOW_SUBDOMAIN = falseMETHODS = GET,POST,PUT,PATCH,DELETE,OPTIONSMAX_AGE = 600ALLOW_CREDENTIALS = falseapp.ini 位置:Windows 一般在
C:\Program Files\Gitea\custom\conf\app.ini,Linux 在/etc/gitea/app.ini;也可以在 Gitea 后台「站点管理 → 配置」里看到实际路径。 若从局域网其他电脑访问,把ALLOW_DOMAIN改成对应主机名/IP。
启动与访问
npm start
# 打开 http://localhost:3000/admin/index.html (若 /admin/ 被 Docusaurus 路由拦截就用这个)
点 Login with Gitea → 授权后即可编辑,保存即提交到 admin/docusaurus 的 main 分支。
排错
- 登录跳转后报
invalid client/ 授权页打不开:client_id没填或填错 → 检查backend.client_id - 登录成功但列表/保存报 CORS 错误(控制台
Access-Control-Allow-Origin):Gitea 的[cors]没开或ALLOW_DOMAIN没包含localhost:3000,或 Gitea 没重启 - 重定向 URI 不匹配(Gitea 报
invalid redirect_uri):Gitea 里 OAuth 应用的重定向 URI 必须与浏览器地址栏实际访问的 CMS 地址逐字符一致 - 分支不对:确认
branch: main与仓库默认分支一致
部署到服务器(115.190.192.69,站点 3000 端口 / Gitea 3011 端口)
站点和 Gitea 放同一台服务器,浏览器访问 http://115.190.192.69:3000/admin/,CMS 直连 Gitea(3011)。
1. 构建
npm run build # 产物在 build/
2. 把代码弄到服务器(推荐源码方式,便于后续自动重建)
# 服务器上(需装 node ≥ 20)
git clone http://115.190.192.69:3011/admin/docusaurus.git
cd docusaurus
npm install
3. 启动站点(3000 端口)
npm run build && npm run serve -- --host 0.0.0.0 --port 3000
# 或用 nginx/caddy 把 build/ 目录静态托管在 3000 端口
# 防火墙放行 3000 端口
4. Gitea 侧(一次性,改完重启 Gitea)
-
OAuth 应用的重定向 URI 加一行:
http://115.190.192.69:3000/admin/(本地调试的http://localhost:3000/admin/可保留,多行添加) -
app.ini的 CORS:[cors]ENABLED = trueSCHEME = httpALLOW_DOMAIN = 115.190.192.69:3000ALLOW_SUBDOMAIN = falseMETHODS = GET,POST,PUT,PATCH,DELETE,OPTIONSMAX_AGE = 600ALLOW_CREDENTIALS = false
5. 访问 http://115.190.192.69:3000/admin/ → Login with Gitea
内容更新闭环(关键)
CMS 里保存 = 直接 commit 到 Gitea 仓库,但静态站点不会自动更新,需要服务器重建:
- 方案 A(推荐):Gitea 建 Push Webhook(URL 指向服务器上的小脚本,如
http://115.190.192.69:8080/hook),脚本收到推送后执行git pull && npm run build,再重启/重载静态服务 - 方案 B(手动):改完内容后 SSH 上服务器
git pull && npm run build - 方案 C(省事):服务器定时任务每分钟
git pull,有变更才 build
tina-cms(卡)
https://tina.io/docs/reference/config
1.npm install tinacms @tinacms/cli
2.tina/config.js
3.package.json
// scripts 里加
"tina": "tinacms dev -c \"docusaurus start\"",
4.npm run tina
自动生成tina/__generated__/
├── _graphql.json
├── _lookup.json
├── _schema.json
├── client.ts
├── config.prebuild.jsx
├── frags.gql
├── queries.gql
├── schema.gql
├── static-media.json
├── types.js
└── types.ts
5.npm run tina
# 官方提供标准版
const branch =
process.env.NEXT_PUBLIC_TINA_BRANCH ||
process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF ||
process.env.HEAD ||
'main';
export default defineConfig({
branch,
// generated by TinaCloud
token: '<Your Read Only Token>',
// generated by TinaCloud
clientId: '<Your Client ID>',
build: {
publicFolder: 'public',
outputFolder: 'admin',
},
schema: {
//Content model definition here...
},
ui: {
previewUrl: (context) => {
// Use Vercel preview deployments based on branch names
return { url: `https://my-app-git-${context.branch}.vercel.app` };
},
},
media: {
tina: {
publicFolder: 'public',
mediaRoot: 'uploads',
},
},
search: {
tina: {
indexerToken: process.env.TINA_SEARCH_TOKEN,
},
},
});
import { defineConfig } from "tinacms";
export default defineConfig({
branch: process.env.TINA_BRANCH || "master",
clientId: process.env.TINA_CLIENT_ID || "",
token: process.env.TINA_TOKEN || "",
build: {
publicFolder: "static",
outputFolder: "admin",
},
media: {
tina: {
mediaRoot: "img",
publicFolder: "static",
static: true,
},
},
schema: {
collections: [
{
name: "blog",
label: "博客",
path: "blog",
format: "md",
fields: [
{ type: "string", name: "title", label: "标题", isTitle: true, required: true },
{ type: "datetime", name: "date", label: "发布日期", ui: { dateFormat: "YYYY-MM-DD", timeFormat: false } },
{ type: "string", name: "tags", label: "标签", list: true, ui: { component: "tags" } },
{ type: "string", name: "description", label: "描述/摘要" },
{ type: "rich-text", name: "body", label: "正文", isBody: true },
],
},
{
name: "docs",
label: "文档",
path: "docs",
format: "md",
fields: [
{ type: "string", name: "title", label: "标题", isTitle: true, required: true },
{ type: "number", name: "sidebar_position", label: "侧边栏位置" },
{ type: "string", name: "tags", label: "标签", list: true, ui: { component: "tags" } },
{ type: "rich-text", name: "body", label: "正文", isBody: true },
],
},
],
},
});