mcpField
T
tinacms

tinacms

github.com/jackspace开发1640 次安装12 周前更新
代码仓库

关于 tinacms

这是用于将 TinaCMS 集成到内容型 Web 应用的实用技能,涵盖多种框架的配置、Schema 设计和故障排查,适合构建支持可视化编辑的 Git 内容网站。

主要功能

  1. 01各框架配置指南
  2. 02Schema 与集合建模
  3. 03可视化编辑集成
  4. 04配置与错误排查

适用场景

  1. 01为 Next.js 网站添加 CMS
  2. 02建模博客或文档内容
  3. 03为编辑者启用实时预览

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jackspace/claudeskillz --skill tinacms

安装后在 Claude Code 中输入/tinacms确认已加载。

附带文件README.mdSKILL.json

详细介绍

TinaCMS 技能

用于将 TinaCMS 集成到现代 Web 应用中的完整技能。


什么是 TinaCMS?

TinaCMS 是一个开源、由 Git 支持的无头内容管理系统(CMS),可让开发者和内容创作者在内容丰富的网站上无缝协作。

主要功能
  1. 由 Git 支持的存储

    • 内容以 Markdown、MDX 或 JSON 文件的形式存储在 Git 仓库中
    • 完整的版本控制和变更历史
    • 不受供应商锁定——内容保存在你的仓库中
  2. 可视化/上下文编辑

    • 并排编辑体验
    • 输入时实时预览更改
    • 针对 Markdown 内容的类所见即所得编辑
  3. 由架构驱动的内容建模

    • 在代码中定义内容结构(tina/config.ts)
    • 根据架构自动生成类型安全的 GraphQL API
    • 用于组织内容的集合和字段系统
  4. 灵活部署

    • TinaCloud:托管服务(最简单,提供免费层级)
    • 自托管:Cloudflare Workers、Vercel Functions、Netlify Functions、AWS Lambda
    • 多种身份验证选项(Auth.js、自定义、本地开发)
  5. 框架支持

    • 最佳:Next.js(App Router + Pages Router)
    • 良好:React、Astro(实验性的可视化编辑)、Gatsby、Hugo、Jekyll、Remix、11ty
    • 框架无关:适用于任何框架(可视化编辑仅限 React)
当前版本
  • tinacms:2.9.0(2025 年 9 月)
  • @tinacms/cli:1.11.0(2025 年 10 月)
  • React 支持:19.x(>=18.3.1 <20.0.0)

何时使用此技能

✅ 适合使用 TinaCMS 的场景:
  1. 构建内容丰富的网站

    • 博客和个人网站
    • 文档网站
    • 营销网站
    • 作品集网站
  2. 非技术编辑人员需要访问权限

    • 不具备编程知识的内容团队
    • 管理页面的营销团队
    • 撰写博客文章的作者
  3. 需要基于 Git 的工作流

    • 希望通过 Git 对内容进行版本管理
    • 需要通过拉取请求审核内容
    • 希望将内容与代码一起保存在仓库中
  4. 需要可视化编辑

    • 编辑人员希望实时查看更改
    • 偏好所见即所得的体验
    • 并排编辑工作流
❌ 不适合使用 TinaCMS 的场景:
  1. 需要实时协作

    • 多个用户同时编辑(类似 Google Docs)
    • 改用 Sanity、Contentful 或 Firebase
  2. 数据高度动态

    • 库存频繁变动的电商产品目录
    • 实时仪表板
    • 改用传统数据库(D1、PostgreSQL)
  3. 不需要内容管理

    • 应用由数据驱动,而非内容驱动
    • 硬编码内容已足够

按框架划分的设置模式

根据所选框架使用相应的设置模式。

1. Next.js 设置(推荐)
App Router(Next.js 13+)

步骤:

  1. 初始化 TinaCMS:

    npx @tinacms/cli@latest init
    
    • 当提示输入公共资源目录时,输入 public
  2. 更新 package.json 脚本:

    {
      "scripts": {
        "dev": "tinacms dev -c \"next dev\"",
        "build": "tinacms build && next build",
        "start": "tinacms build && next start"
      }
    }
    
  3. 设置环境变量:

    # .env.local
    NEXT_PUBLIC_TINA_CLIENT_ID=your_client_id
    TINA_TOKEN=your_read_only_token
    
  4. 启动开发服务器:

    npm run dev
    
  5. 访问管理界面:

    http://localhost:3000/admin/index.html
    

创建的关键文件:

  • tina/config.ts - 架构配置
  • app/admin/[[...index]]/page.tsx - 管理界面路由(如果使用 App Router)

模板:参见 templates/nextjs/tina-config-app-router.ts


Pages Router(Next.js 12 及以下版本)

设置完全相同,但管理路由为:

  • 使用 pages/admin/[[...index]].tsx,而不是 app 目录

数据获取模式:

// pages/posts/[slug].tsx
import { client } from '../../tina/__generated__/client'
import { useTina } from 'tinacms/dist/react'

export default function BlogPost(props) {
  // Hydrate for visual editing
  const { data } = useTina({
    query: props.query,
    variables: props.variables,
    data: props.data
  })

  return (
    <article>
      <h1>{data.post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: data.post.body }} />
    </article>
  )
}

export async function getStaticProps({ params }) {
  const response = await client.queries.post({
    relativePath: `${params.slug}.md`
  })

  return {
    props: {
      data: response.data,
      query: response.query,
      variables: response.variables
    }
  }
}

export async function getStaticPaths() {
  const response = await client.queries.postConnection()
  const paths = response.data.postConnection.edges.map((edge) => ({
    params: { slug: edge.node._sys.filename }
  }))

  return { paths, fallback: 'blocking' }
}

模板:参见 templates/nextjs/tina-config-pages-router.ts


2. Vite + React 设置

步骤:

  1. 安装依赖:

    npm install react@^19 react-dom@^19 tinacms
    
  2. 初始化 TinaCMS:

    npx @tinacms/cli@latest init
    
    • 将公共资源目录设置为 public
  3. 更新 vite.config.ts:

    import { defineConfig } from 'vite'
    import react from '@vitejs/plugin-react'
    
    export default defineConfig({
      plugins: [react()],
      server: {
        port: 3000  // TinaCMS default
      }
    })
    
  4. 更新 package.json 脚本:

    {
      "scripts": {
        "dev": "tinacms dev -c \"vite\"",
        "build": "tinacms build && vite build",
        "preview": "vite preview"
      }
    }
    
  5. 创建管理界面:

    选项 A:手动路由(React Router)

    // src/pages/Admin.tsx
    import TinaCMS from 'tinacms'
    
    export default function Admin() {
      return <div id="tina-admin" />
    }
    

    选项 B:直接使用 HTML

    
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>Tina CMS</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
      </head>
      <body>
        <div id="root"></div>
        <script type="module" src="/@fs/[path-to-tina-admin]"></script>
      </body>
    </html>
    
  6. 使用 useTina hook 进行可视化编辑:

    import { useTina } from 'tinacms/dist/react'
    import { client } from '../tina/__generated__/client'
    
    function BlogPost({ initialData }) {
      const { data } = useTina({
        query: initialData.query,
        variables: initialData.variables,
        data: initialData.data
      })
    
      return (
        <article>
          <h1>{data.post.title}</h1>
          <div>{/* render body */}</div>
        </article>
      )
    }
    
  7. 设置环境变量:

    # .env
    VITE_TINA_CLIENT_ID=your_client_id
    VITE_TINA_TOKEN=your_read_only_token
    

模板:参见 templates/vite-react/


3. Astro 设置

步骤:

  1. 使用官方入门模板(推荐):

    npx create-tina-app@latest --template tina-astro-starter
    

    或手动初始化:

    npx @tinacms/cli@latest init
    
  2. 更新 package.json 脚本:

    {
      "scripts": {
        "dev": "tinacms dev -c \"astro dev\"",
        "build": "tinacms build && astro build",
        "preview": "astro preview"
      }
    }
    
  3. 配置 Astro:

    // astro.config.mjs
    import { defineConfig } from 'astro/config'
    import react from '@astro/react'
    
    export default defineConfig({
      integrations: [react()]  // Required for Tina admin
    })
    
  4. 可视化编辑(实验性):

    • 需要 React 组件
    • 使用 client:tinaDirective 进行交互式编辑
    • 截至 2025 年 10 月,可视化编辑仍处于实验阶段
  5. 设置环境变量:

    # .env
    PUBLIC_TINA_CLIENT_ID=your_client_id
    TINA_TOKEN=your_read_only_token
    

适用场景:以内容为主的静态网站、文档、博客

模板:参见 templates/astro/


4. 框架无关的设置

适用范围:Hugo、Jekyll、Eleventy、Gatsby、Remix 或任何框架

步骤:

  1. 初始化 TinaCMS:

    npx @tinacms/cli@latest init
    
  2. 手动配置构建脚本:

    {
      "scripts": {
        "dev": "tinacms dev -c \"<your-dev-command>\"",
        "build": "tinacms build && <your-build-command>"
      }
    }
    
  3. 管理界面:

    • 自动创建于 http://localhost:<port>/admin/index.html
    • 端口取决于所用框架
  4. 数据获取:

    • 不支持可视化编辑(仅侧边栏)
    • 通过由 Git 提供支持的界面编辑内容
    • 更改会直接保存到文件
  5. 设置环境变量:

    TINA_CLIENT_ID=your_client_id
    TINA_TOKEN=your_read_only_token
    

限制:

  • 不支持可视化编辑(仅限 React 的功能)
  • 需要手动集成
  • 仅支持基于侧边栏的编辑

Schema 建模最佳实践

在 tina/config.ts 中定义内容结构。

基本配置结构
import { defineConfig } from 'tinacms'

export default defineConfig({
  // Branch configuration
  branch: process.env.GITHUB_BRANCH ||
          process.env.VERCEL_GIT_COMMIT_REF ||
          'main',

  // TinaCloud credentials (if using managed service)
  clientId: process.env.NEXT_PUBLIC_TINA_CLIENT_ID,
  token: process.env.TINA_TOKEN,

  // Build configuration
  build: {
    outputFolder: 'admin',
    publicFolder: 'public',
  },

  // Media configuration
  media: {
    tina: {
      mediaRoot: '',
      publicFolder: 'public',
    },
  },

  // Content schema
  schema: {
    collections: [
      // Define collections here
    ],
  },
})

集合

集合 = 内容类型 + 目录映射

{
  name: 'post',           // Singular, internal name (used in API)
  label: 'Blog Posts',    // Plural, display name (shown in admin)
  path: 'content/posts',  // Directory where files are stored
  format: 'mdx',          // File format: md, mdx, markdown, json, yaml, toml
  fields: [/* ... */]     // Array of field definitions
}

关键属性:

  • name:内部标识符(仅限字母数字字符和下划线)
  • label:管理界面中显示的易读名称
  • path:相对于项目根目录的文件路径
  • format:文件扩展名(默认为 'md')
  • fields:内容结构定义

字段类型参考
类型用途示例
string短文本(单行)标题、slug、作者姓名
rich-text长格式内容博客正文、页面内容
number数值价格、数量、评分
datetime日期/时间值发布日期、活动时间
boolean真/假开关草稿状态、精选标记
image图片上传主图、缩略图、头像
reference链接到其他文档作者、类别、相关文章
object嵌套字段组SEO 元数据、社交链接

完整参考:参见 references/field-types-reference.md


集合模板
博客文章集合
{
  name: 'post',
  label: 'Blog Posts',
  path: 'content/posts',
  format: 'mdx',
  fields: [
    {
      type: 'string',
      name: 'title',
      label: 'Title',
      isTitle: true,  // Shows in content list
      required: true
    },
    {
      type: 'string',
      name: 'excerpt',
      label: 'Excerpt',
      ui: {
        component: 'textarea'  // Multi-line input
      }
    },
    {
      type: 'image',
      name: 'coverImage',
      label: 'Cover Image'
    },
    {
      type: 'datetime',
      name: 'date',
      label: 'Published Date',
      required: true
    },
    {
      type: 'reference',
      name: 'author',
      label: 'Author',
      collections: ['author']  // References author collection
    },
    {
      type: 'boolean',
      name: 'draft',
      label: 'Draft',
      description: 'If checked, post will not be published',
      required: true
    },
    {
      type: 'rich-text',
      name: 'body',
      label: 'Body',
      isBody: true  // Main content area
    }
  ],
  ui: {
    router: ({ document }) => `/blog/${document._sys.filename}`
  }
}

模板:参见 templates/collections/blog-post.ts


文档页面集合
{
  name: 'doc',
  label: 'Documentation',
  path: 'content/docs',
  format: 'mdx',
  fields: [
    {
      type: 'string',
      name: 'title',
      label: 'Title',
      isTitle: true,
      required: true
    },
    {
      type: 'string',
      name: 'description',
      label: 'Description',
      ui: {
        component: 'textarea'
      }
    },
    {
      type: 'number',
      name: 'order',
      label: 'Order',
      description: 'Sort order in sidebar'
    },
    {
      type: 'rich-text',
      name: 'body',
      label: 'Body',
      isBody: true,
      templates: [
        // MDX components can be defined here
      ]
    }
  ],
  ui: {
    router: ({ document }) => {
      const breadcrumbs = document._sys.breadcrumbs.join('/')
      return `/docs/${breadcrumbs}`
    }
  }
}

模板:参见 templates/collections/doc-page.ts


作者集合(引用目标)
{
  name: 'author',
  label: 'Authors',
  path: 'content/authors',
  format: 'json',  // Use JSON for structured data
  fields: [
    {
      type: 'string',
      name: 'name',
      label: 'Name',
      isTitle: true,
      required: true
    },
    {
      type: 'string',
      name: 'email',
      label: 'Email',
      ui: {
        validate: (value) => {
          if (!value?.includes('@')) {
            return 'Invalid email address'
          }
        }
      }
    },
    {
      type: 'image',
      name: 'avatar',
      label: 'Avatar'
    },
    {
      type: 'string',
      name: 'bio',
      label: 'Bio',
      ui: {
        component: 'textarea'
      }
    },
    {
      type: 'object',
      name: 'social',
      label: 'Social Links',
      fields: [
        {
          type: 'string',
          name: 'twitter',
          label: 'Twitter'
        },
        {
          type: 'string',
          name: 'github',
          label: 'GitHub'
        }
      ]
    }
  ]
}

模板:参见 templates/collections/author.ts


着陆页集合(多个模板)
{
  name: 'page',
  label: 'Pages',
  path: 'content/pages',
  format: 'mdx',
  templates: [  // Multiple templates for different page types
    {
      name: 'basic',
      label: 'Basic Page',
      fields: [
        {
          type: 'string',
          name: 'title',
          label: 'Title',
          isTitle: true,
          required: true
        },
        {
          type: 'rich-text',
          name: 'body',
          label: 'Body',
          isBody: true
        }
      ]
    },
    {
      name: 'landing',
      label: 'Landing Page',
      fields: [
        {
          type: 'string',
          name: 'title',
          label: 'Title',
          isTitle: true,
          required: true
        },
        {
          type: 'object',
          name: 'hero',
          label: 'Hero Section',
          fields: [
            {
              type: 'string',
              name: 'headline',
              label: 'Headline'
            },
            {
              type: 'string',
              name: 'subheadline',
              label: 'Subheadline',
              ui: { component: 'textarea' }
            },
            {
              type: 'image',
              name: 'image',
              label: 'Hero Image'
            }
          ]
        },
        {
          type: 'object',
          name: 'cta',
          label: 'Call to Action',
          fields: [
            {
              type: 'string',
              name: 'text',
              label: 'Button Text'
            },
            {
              type: 'string',
              name: 'url',
              label: 'Button URL'
            }
          ]
        }
      ]
    }
  ]
}

使用模板时:文档的 frontmatter 中必须包含 _template 字段:

---
_template: landing
title: My Landing Page
---

模板:参见 templates/collections/landing-page.ts


常见错误与解决方案

1. ❌ ESbuild 编译错误

错误消息:

ERROR: Schema Not Successfully Built
ERROR: Config Not Successfully Executed

原因:

  • 导入使用自定义加载器(webpack、babel 插件、esbuild 加载器)的代码
  • 导入仅供前端使用的代码(使用 window、DOM API、React 钩子)
  • 导入整个组件库,而不是特定模块

解决方案:

仅导入所需内容:

// ❌ Bad - Imports entire component directory
import { HeroComponent } from '../components/'

// ✅ Good - Import specific file
import { HeroComponent } from '../components/blocks/hero'

预防建议:

  • 尽量减少 tina/config.ts 中的导入
  • 仅导入类型定义和简单的工具函数
  • 避免直接导入 UI 组件
  • 如有需要,创建单独的 .schema.ts 文件

参考:请参阅 references/common-errors.md#esbuild


2. ❌ 模块解析:“Could not resolve 'tinacms'”

错误消息:

Error: Could not resolve "tinacms"

原因:

  • 安装损坏或不完整
  • 依赖项之间的版本不匹配
  • 缺少对等依赖项

解决方案:

# Clear cache and reinstall
rm -rf node_modules package-lock.json
npm install

# Or with pnpm
rm -rf node_modules pnpm-lock.yaml
pnpm install

# Or with yarn
rm -rf node_modules yarn.lock
yarn install

预防措施:

  • 使用锁文件(package-lock.json、pnpm-lock.yaml、yarn.lock)
  • 不要使用 --no-optional 或 --omit=optional 标志
  • 确保已安装 react 和 react-dom(即使使用非 React 框架)

3. ❌ 字段命名限制

错误消息:

Field name contains invalid characters

原因:

  • TinaCMS 字段名称只能包含字母、数字和下划线
  • 不允许使用连字符、空格和特殊字符

解决方案:

// ❌ Bad - Uses hyphens
{
  name: 'hero-image',
  label: 'Hero Image',
  type: 'image'
}

// ❌ Bad - Uses spaces
{
  name: 'hero image',
  label: 'Hero Image',
  type: 'image'
}

// ✅ Good - Uses underscores
{
  name: 'hero_image',
  label: 'Hero Image',
  type: 'image'
}

// ✅ Good - CamelCase also works
{
  name: 'heroImage',
  label: 'Hero Image',
  type: 'image'
}

注意:这是从 Forestry.io 迁移时的破坏性变更


4. ❌ Docker 绑定问题

错误:

  • 无法从 Docker 容器外部访问 TinaCMS 管理界面

原因:

  • TinaCMS 默认绑定到 127.0.0.1(仅限本地主机)
  • Docker 容器需要绑定到 0.0.0.0 才能接受外部连接

解决方案:

# Ensure framework dev server listens on all interfaces
tinacms dev -c "next dev --hostname 0.0.0.0"
tinacms dev -c "vite --host 0.0.0.0"
tinacms dev -c "astro dev --host 0.0.0.0"

Docker Compose 示例:

services:
  app:
    build: .
    ports:
      - "3000:3000"
    command: npm run dev  # Which runs: tinacms dev -c "next dev --hostname 0.0.0.0"

5. ❌ 缺少 _template 键错误

错误消息:

GetCollection failed: Unable to fetch
template name was not provided

原因:

  • 集合使用 templates 数组(多个架构)
  • 文档的 frontmatter 中缺少 _template 字段
  • 从 templates 迁移到 fields,但文档尚未更新

解决方案:

选项 1:改用 fields(单个模板时推荐)

{
  name: 'post',
  path: 'content/posts',
  fields: [/* ... */]  // No _template needed
}

选项 2:确保 frontmatter 中存在 _template

---
_template: article  # ← Required when using templates array
title: My Post
---

迁移脚本(从 templates 转换为 fields 时):

# Remove _template from all files in content/posts/
find content/posts -name "*.md" -exec sed -i '/_template:/d' {} +

6. ❌ 路径不匹配问题

错误:

  • 文件未显示在 Tina 管理界面中
  • 保存时出现“找不到文件”错误
  • GraphQL 查询返回空结果

原因:

  • 集合配置中的 path 与实际文件目录不匹配
  • 相对路径与绝对路径混淆
  • 末尾斜杠问题

解决方案:

// Files located at: content/posts/hello.md

// ✅ Correct
{
  name: 'post',
  path: 'content/posts',  // Matches file location
  fields: [/* ... */]
}

// ❌ Wrong - Missing 'content/'
{
  name: 'post',
  path: 'posts',  // Files won't be found
  fields: [/* ... */]
}

// ❌ Wrong - Trailing slash
{
  name: 'post',
  path: 'content/posts/',  // May cause issues
  fields: [/* ... */]
}

调试:

  1. 运行 npx @tinacms/cli@latest audit 检查路径
  2. 确认文件存在于指定目录中
  3. 检查文件扩展名是否与 format 字段匹配

7. ❌ 构建脚本顺序问题

错误消息:

ERROR: Cannot find module '../tina/__generated__/client'
ERROR: Property 'queries' does not exist on type '{}'

原因:

  • 在 tinacms build 之前运行框架构建
  • TypeScript 编译前未生成 Tina 类型
  • CI/CD 流水线顺序不正确

解决方案:

{
  "scripts": {
    "build": "tinacms build && next build"  // ✅ Tina FIRST
    // NOT: "build": "next build && tinacms build"  // ❌ Wrong order
  }
}

CI/CD 示例(GitHub Actions):

- name: Build
  run: |
    npx @tinacms/cli@latest build  # Generate types first
    npm run build                   # Then build framework

原因说明:

  • tinacms build 会在 tina/__generated__/ 中生成 TypeScript 类型
  • 框架构建需要这些类型才能成功编译
  • 顺序错误会导致类型错误

8. ❌ TinaCMS 资源加载失败

错误消息:

Failed to load resource: net::ERR_CONNECTION_REFUSED
http://localhost:4001/...

原因:

  • 将开发环境的 admin/index.html 推送到生产环境(会从 localhost 加载资源)
  • 网站部署在子目录中,但未配置 basePath

解决方案:

生产环境部署:

{
  "scripts": {
    "build": "tinacms build && next build"  // ✅ Always build
    // NOT: "build": "tinacms dev"          // ❌ Never dev in production
  }
}

子目录部署:

// tina/config.ts
export default defineConfig({
  build: {
    outputFolder: 'admin',
    publicFolder: 'public',
    basePath: 'your-subdirectory'  // ← Set if site not at domain root
  }
})

CI/CD 修复:

# GitHub Actions / Vercel / Netlify
- run: npx @tinacms/cli@latest build  # Always use build, not dev

9. ❌ 引用字段出现 503 Service Unavailable

错误:

  • 引用字段下拉菜单超时并返回 503 错误
  • 加载引用字段时,管理界面变得无响应

原因:

  • 被引用的集合中项目过多(数百或数千个)
  • 目前引用字段不支持分页

解决方案:

选项 1:拆分集合

// Instead of one huge "authors" collection
// Split by active status or alphabetically

{
  name: 'active_author',
  label: 'Active Authors',
  path: 'content/authors/active',
  fields: [/* ... */]
}

{
  name: 'archived_author',
  label: 'Archived Authors',
  path: 'content/authors/archived',
  fields: [/* ... */]
}

选项 2:使用带验证的字符串字段

// Instead of reference
{
  type: 'string',
  name: 'authorId',
  label: 'Author ID',
  ui: {
    component: 'select',
    options: ['author-1', 'author-2', 'author-3']  // Curated list
  }
}

选项 3:自定义字段组件(高级)


部署模式

选择适合你需求的部署方式。

选项 1:TinaCloud(托管)- 最简单 ⭐

适用于:快速设置、免费套餐、托管基础设施

步骤:

  1. 注册:https://app.tina.io
  2. 创建项目,获取 Client ID 和 Read Only Token
  3. 设置环境变量:
    NEXT_PUBLIC_TINA_CLIENT_ID=your_client_id
    TINA_TOKEN=your_read_only_token
    
  4. 初始化后端:
    npx @tinacms/cli@latest init backend
    
  5. 部署到托管服务商(Vercel、Netlify、Cloudflare Pages)
  6. 在 Tina 控制面板中设置 GitHub 集成

优点:

  • ✅ 无需配置后端
  • ✅ 自动提供 GraphQL API
  • ✅ 内置身份验证
  • ✅ 自动处理 Git 集成
  • ✅ 免费套餐额度充足(每月 10k 次请求)

缺点:

  • ❌ 超出免费套餐后需要付费
  • ❌ 依赖服务商(不过内容仍保存在 Git 中)

参考:参阅 references/deployment-guide.md#tinacloud


选项 2:在 Cloudflare Workers 上自托管 🔥

适用于:完全掌控、Cloudflare 生态、边缘部署

步骤:

  1. 安装依赖:

    npm install @tinacms/datalayer tinacms-authjs
    
  2. 初始化后端:

    npx @tinacms/cli@latest init backend
    
  3. 创建 Workers 端点:

    // workers/src/index.ts
    import { TinaNodeBackend, LocalBackendAuthProvider } from '@tinacms/datalayer'
    import { AuthJsBackendAuthProvider, TinaAuthJSOptions } from 'tinacms-authjs'
    import databaseClient from '../../tina/__generated__/databaseClient'
    
    const isLocal = process.env.TINA_PUBLIC_IS_LOCAL === 'true'
    
    export default {
      async fetch(request: Request, env: Env) {
        const handler = TinaNodeBackend({
          authProvider: isLocal
            ? LocalBackendAuthProvider()
            : AuthJsBackendAuthProvider({
                authOptions: TinaAuthJSOptions({
                  databaseClient,
                  secret: env.NEXTAUTH_SECRET,
                }),
              }),
          databaseClient,
        })
    
        return handler(request)
      }
    }
    
  4. 更新 tina/config.ts:

    export default defineConfig({
      contentApiUrlOverride: '/api/tina/gql',  // Your Workers endpoint
      // ... rest of config
    })
    
  5. 配置 wrangler.jsonc:

    {
      "name": "tina-backend",
      "main": "workers/src/index.ts",
      "compatibility_date": "2025-10-24",
      "vars": {
        "TINA_PUBLIC_IS_LOCAL": "false"
      },
      "env": {
        "production": {
          "vars": {
            "NEXTAUTH_SECRET": "your-secret-here"
          }
        }
      }
    }
    
  6. 部署:

    npx wrangler deploy
    

优点:

  • ✅ 完全掌控后端
  • ✅ 免费套餐额度充足(每天 100k 次请求)
  • ✅ 全球边缘网络(全球访问速度快)
  • ✅ 不受服务商锁定

缺点:

  • ❌ 设置较复杂
  • ❌ 需要配置身份验证
  • ❌ 需要了解 Cloudflare Workers

完整指南:参阅 references/self-hosting-cloudflare.md

模板:参阅 templates/cloudflare-worker-backend/


选项 3:在 Vercel Functions 上自托管

适用于:Next.js 项目、Vercel 生态

步骤:

  1. 安装依赖:

    npm install @tinacms/datalayer tinacms-authjs
    
  2. 创建 API 路由:

    // api/tina/backend.ts
    import { TinaNodeBackend, LocalBackendAuthProvider } from '@tinacms/datalayer'
    import { AuthJsBackendAuthProvider, TinaAuthJSOptions } from 'tinacms-authjs'
    import databaseClient from '../../../tina/__generated__/databaseClient'
    
    const isLocal = process.env.TINA_PUBLIC_IS_LOCAL === 'true'
    
    const handler = TinaNodeBackend({
      authProvider: isLocal
        ? LocalBackendAuthProvider()
        : AuthJsBackendAuthProvider({
            authOptions: TinaAuthJSOptions({
              databaseClient,
              secret: process.env.NEXTAUTH_SECRET,
            }),
          }),
      databaseClient,
    })
    
    export default handler
    
  3. 创建 vercel.json 重写规则:

    {
      "rewrites": [
        {
          "source": "/api/tina/:path*",
          "destination": "/api/tina/backend"
        }
      ]
    }
    
  4. 更新开发脚本:

    {
      "scripts": {
        "dev": "TINA_PUBLIC_IS_LOCAL=true tinacms dev -c \"next dev --port $PORT\""
      }
    }
    
  5. 在 Vercel 控制面板中设置环境变量:

    NEXTAUTH_SECRET=your-secret
    TINA_PUBLIC_IS_LOCAL=false
    
  6. 部署:

    vercel deploy
    

优点:

  • ✅ 与 Next.js 原生集成
  • ✅ Vercel 部署简单
  • ✅ 无服务器(自动扩缩)

缺点:

  • ❌ 仅适用于 Vercel
  • ❌ 函数限制(10s 超时、50MB 大小)

参考:参阅 references/deployment-guide.md#vercel


选项 4:在 Netlify Functions 上自托管

步骤:

  1. 安装依赖:

    npm install express serverless-http @tinacms/datalayer tinacms-authjs
    
  2. 创建函数:

    // netlify/functions/tina.ts
    import express from 'express'
    import ServerlessHttp from 'serverless-http'
    import { TinaNodeBackend, LocalBackendAuthProvider } from '@tinacms/datalayer'
    import { AuthJsBackendAuthProvider, TinaAuthJSOptions } from 'tinacms-authjs'
    import databaseClient from '../../tina/__generated__/databaseClient'
    
    const app = express()
    app.use(express.json())
    
    const tinaBackend = TinaNodeBackend({
      authProvider: AuthJsBackendAuthProvider({
        authOptions: TinaAuthJSOptions({
          databaseClient,
          secret: process.env.NEXTAUTH_SECRET,
        }),
      }),
      databaseClient,
    })
    
    app.post('/api/tina/*', tinaBackend)
    app.get('/api/tina/*', tinaBackend)
    
    export const handler = ServerlessHttp(app)
    
  3. 创建 netlify.toml:

    [functions]
      node_bundler = "esbuild"
    
    [[redirects]]
      from = "/api/tina/*"
      to = "/.netlify/functions/tina"
      status = 200
      force = true
    
  4. 部署:

    netlify deploy --prod
    

参考:参阅 references/deployment-guide.md#netlify


身份验证设置

选项 1:本地开发(默认)

用途:本地开发,不部署到生产环境

// tina/__generated__/databaseClient or backend config
const isLocal = process.env.TINA_PUBLIC_IS_LOCAL === 'true'

authProvider: isLocal ? LocalBackendAuthProvider() : /* ... */

环境变量:

TINA_PUBLIC_IS_LOCAL=true

安全性:没有身份验证——仅在本地使用!


选项 2:Auth.js(自托管推荐)

用途:搭配 OAuth 提供商(GitHub、Discord、Google 等)进行自托管

安装:

npm install next-auth tinacms-authjs

配置:

import { AuthJsBackendAuthProvider, TinaAuthJSOptions } from 'tinacms-authjs'
import DiscordProvider from 'next-auth/providers/discord'

export const AuthOptions = TinaAuthJSOptions({
  databaseClient,
  secret: process.env.NEXTAUTH_SECRET,
  providers: [
    DiscordProvider({
      clientId: process.env.DISCORD_CLIENT_ID,
      clientSecret: process.env.DISCORD_CLIENT_SECRET,
    }),
    // Add GitHub, Google, etc.
  ],
})

const handler = TinaNodeBackend({
  authProvider: AuthJsBackendAuthProvider({
    authOptions: AuthOptions,
  }),
  databaseClient,
})

支持的提供商:GitHub、Discord、Google、Twitter、Facebook、Email 等

参考:https://next-auth.js.org/providers/


选项 3:TinaCloud Auth(托管)

用途:TinaCloud 托管服务

import { TinaCloudBackendAuthProvider } from '@tinacms/auth'

authProvider: TinaCloudBackendAuthProvider()

设置:

  1. 在 https://app.tina.io 注册
  2. 创建项目
  3. 在控制面板中管理用户
  4. 自动处理身份验证

选项 4:自定义身份验证提供商

用途:已有身份验证系统、自定义需求

const CustomBackendAuth = () => {
  return {
    isAuthorized: async (req, res) => {
      const token = req.headers.authorization

      // Your validation logic
      const user = await validateToken(token)

      if (user && user.canEdit) {
        return { isAuthorized: true }
      }

      return {
        isAuthorized: false,
        errorMessage: 'Unauthorized',
        errorCode: 401
      }
    },
  }
}

authProvider: CustomBackendAuth()

GraphQL API 用法

TinaCMS 会自动生成类型安全的 GraphQL 客户端。

查询数据

TinaCloud:

import client from '../tina/__generated__/client'

// Single document
const post = await client.queries.post({
  relativePath: 'hello-world.md'
})

// Multiple documents
const posts = await client.queries.postConnection()

自托管:

import client from '../tina/__generated__/databaseClient'

// Same API as TinaCloud client
const post = await client.queries.post({
  relativePath: 'hello-world.md'
})
使用 useTina Hook 进行可视化编辑

Next.js 示例:

import { useTina } from 'tinacms/dist/react'
import { client } from '../../tina/__generated__/client'

export default function BlogPost(props) {
  // Hydrate data for visual editing
  const { data } = useTina({
    query: props.query,
    variables: props.variables,
    data: props.data
  })

  return (
    <article>
      <h1>{data.post.title}</h1>
      <p>{data.post.excerpt}</p>
      <div dangerouslySetInnerHTML={{ __html: data.post.body }} />
    </article>
  )
}

export async function getStaticProps({ params }) {
  const response = await client.queries.post({
    relativePath: `${params.slug}.md`
  })

  return {
    props: {
      data: response.data,
      query: response.query,
      variables: response.variables
    }
  }
}

工作原理:

  • 在生产环境中:useTina 返回初始数据(无额外开销)
  • 在编辑模式下:useTina 连接到 GraphQL 并实时更新
  • 更改会立即显示在预览中

更多资源

模板
  • templates/nextjs/ - Next.js App Router + Pages Router 配置
  • templates/vite-react/ - Vite + React 设置
  • templates/astro/ - Astro 集成
  • templates/collections/ - 预构建的集合架构
  • templates/cloudflare-worker-backend/ - Cloudflare Workers 自托管
参考资料
  • references/schema-patterns.md - 高级架构建模模式
  • references/field-types-reference.md - 完整的字段类型文档
  • references/deployment-guide.md - 适用于所有平台的部署指南
  • references/self-hosting-cloudflare.md - 完整的 Cloudflare Workers 指南
  • references/common-errors.md - 扩展错误排查指南
  • references/migration-guide.md - 从 Forestry.io 迁移
脚本
  • scripts/init-nextjs.sh - 自动设置 Next.js
  • scripts/init-vite-react.sh - 自动设置 Vite + React
  • scripts/init-astro.sh - 自动设置 Astro
  • scripts/check-versions.sh - 验证软件包版本
官方文档

Token 效率

预计节省:65-70%(节省 10,900 个 token)

不使用技能(约 16,000 个 token):

  • 初始研究和探索:3,000 个 token
  • 框架设置试错:2,500 个 token
  • 架构建模尝试:2,000 个 token
  • 错误排查:4,000 个 token
  • 部署配置:2,500 个 token
  • 身份验证设置:2,000 个 token

使用技能(约 5,100 个 token):

  • 技能发现:100 个 token
  • 加载技能(SKILL.md):3,000 个 token
  • 模板选择:500 个 token
  • 少量项目特定调整:1,500 个 token

可预防的错误

此技能可预防 9 种常见错误(预防率为 100%):

  1. ✅ ESbuild 编译错误(导入问题)
  2. ✅ 模块解析问题
  3. ✅ 字段命名约束违规
  4. ✅ Docker 绑定问题
  5. ✅ 缺少 _template 键错误
  6. ✅ 路径不匹配问题
  7. ✅ 构建脚本顺序错误
  8. ✅ 生产环境中的资源加载错误
  9. ✅ 引用字段 503 超时

快速入门示例

示例 1:使用 Next.js + TinaCloud 构建博客
# 1. Create Next.js app
npx create-next-app@latest my-blog --typescript --app

# 2. Initialize TinaCMS
cd my-blog
npx @tinacms/cli@latest init

# 3. Set environment variables
echo "NEXT_PUBLIC_TINA_CLIENT_ID=your_client_id" >> .env.local
echo "TINA_TOKEN=your_token" >> .env.local

# 4. Start dev server
npm run dev

# 5. Access admin
open http://localhost:3000/admin/index.html

示例 2:使用 Astro 构建文档站点
# 1. Use official starter
npx create-tina-app@latest my-docs --template tina-astro-starter

# 2. Install dependencies
cd my-docs
npm install

# 3. Start dev server
npm run dev

# 4. Access admin
open http://localhost:4321/admin/index.html

示例 3:在 Cloudflare Workers 上自托管
# 1. Initialize project
npm create cloudflare@latest my-app

# 2. Add TinaCMS
npx @tinacms/cli@latest init
npx @tinacms/cli@latest init backend

# 3. Install dependencies
npm install @tinacms/datalayer tinacms-authjs

# 4. Copy Cloudflare Workers backend template
cp -r [path-to-skill]/templates/cloudflare-worker-backend/* workers/

# 5. Configure and deploy
npx wrangler deploy

生产环境示例


支持

遇到问题? 请先查看 references/common-errors.md

仍然无法解决?


最后更新:2025-10-24 技能版本:1.0.0 TinaCMS 版本:2.9.0 CLI 版本:1.11.0

评论

支持 Markdown