🎯

tinacms

🎯Skill

from ovachiever/droid-tings

VibeIndex|
What it does

Enables visual, Git-backed content management for modern web applications, supporting Next.js, Vite+React, and Astro with non-technical editor-friendly interfaces.

πŸ“¦

Part of

ovachiever/droid-tings(370 items)

tinacms

Installation

npxRun with npx
npx @tinacms/cli@latest init
npm runRun npm script
npm run dev
npxRun with npx
npx @tinacms/cli@latest build # Generate types first
npm runRun npm script
npm run build # Then build framework
npxRun with npx
npx @tinacms/cli@latest init backend

+ 2 more commands

πŸ“– Extracted from docs: ovachiever/droid-tings
16Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

|

Overview

# TinaCMS Skill

Complete skill for integrating TinaCMS into modern web applications.

---

What is TinaCMS?

TinaCMS is an open-source, Git-backed headless content management system (CMS) that enables developers and content creators to collaborate seamlessly on content-heavy websites.

Key Features

  1. Git-Backed Storage

- Content stored as Markdown, MDX, or JSON files in Git repository

- Full version control and change history

- No vendor lock-in - content lives in your repo

  1. Visual/Contextual Editing

- Side-by-side editing experience

- Live preview of changes as you type

- WYSIWYG-like editing for Markdown content

  1. Schema-Driven Content Modeling

- Define content structure in code (tina/config.ts)

- Type-safe GraphQL API auto-generated from schema

- Collections and fields system for organized content

  1. Flexible Deployment

- TinaCloud: Managed service (easiest, free tier available)

- Self-Hosted: Cloudflare Workers, Vercel Functions, Netlify Functions, AWS Lambda

- Multiple authentication options (Auth.js, custom, local dev)

  1. Framework Support

- Best: Next.js (App Router + Pages Router)

- Good: React, Astro (experimental visual editing), Gatsby, Hugo, Jekyll, Remix, 11ty

- Framework-Agnostic: Works with any framework (visual editing limited to React)

Current Versions

  • tinacms: 2.9.0 (September 2025)
  • @tinacms/cli: 1.11.0 (October 2025)
  • React Support: 19.x (>=18.3.1 <20.0.0)

---

When to Use This Skill

βœ… Use TinaCMS When:

  1. Building Content-Heavy Sites

- Blogs and personal websites

- Documentation sites

- Marketing websites

- Portfolio sites

  1. Non-Technical Editors Need Access

- Content teams without coding knowledge

- Marketing teams managing pages

- Authors writing blog posts

  1. Git-Based Workflow Desired

- Want content versioning through Git

- Need content review through pull requests

- Prefer content in repository with code

  1. Visual Editing Required

- Editors want to see changes live

- WYSIWYG experience preferred

- Side-by-side editing workflow

❌ Don't Use TinaCMS When:

  1. Real-Time Collaboration Needed

- Multiple users editing simultaneously (Google Docs-style)

- Use Sanity, Contentful, or Firebase instead

  1. Highly Dynamic Data

- E-commerce product catalogs with frequent inventory changes

- Real-time dashboards

- Use traditional databases (D1, PostgreSQL) instead

  1. No Content Management Needed

- Application is data-driven, not content-driven

- Hard-coded content is sufficient

---

Setup Patterns by Framework

Use the appropriate setup pattern based on your framework choice.

1. Next.js Setup (Recommended)

#### App Router (Next.js 13+)

Steps:

  1. Initialize TinaCMS:

```bash

npx @tinacms/cli@latest init

```

- When prompted for public assets directory, enter public

  1. Update package.json scripts:

```json

{

"scripts": {

"dev": "tinacms dev -c \"next dev\"",

"build": "tinacms build && next build",

"start": "tinacms build && next start"

}

}

```

  1. Set environment variables:

```env

# .env.local

NEXT_PUBLIC_TINA_CLIENT_ID=your_client_id

TINA_TOKEN=your_read_only_token

```

  1. Start development server:

```bash

npm run dev

```

  1. Access admin interface:

```

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

```

Key Files Created:

  • tina/config.ts - Schema configuration
  • app/admin/[[...index]]/page.tsx - Admin UI route (if using App Router)

Template: See templates/nextjs/tina-config-app-router.ts

---

#### Pages Router (Next.js 12 and below)

Setup is identical, except admin route is:

  • pages/admin/[[...index]].tsx instead of app directory

Data Fetching Pattern:

```tsx

// 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 (

{data.post.title}

)

}

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' }

}

```

Template: See templates/nextjs/tina-config-pages-router.ts

---

2. Vite + React Setup

Steps:

  1. Install dependencies:

```bash

npm install react@^19 react-dom@^19 tinacms

```

  1. Initialize TinaCMS:

```bash

npx @tinacms/cli@latest init

```

- Set public assets directory to public

  1. Update vite.config.ts:

```typescript

import { defineConfig } from 'vite'

import react from '@vitejs/plugin-react'

export default defineConfig({

plugins: [react()],

server: {

port: 3000 // TinaCMS default

}

})

```

  1. Update package.json scripts:

```json

{

"scripts": {

"dev": "tinacms dev -c \"vite\"",

"build": "tinacms build && vite build",

"preview": "vite preview"

}

}

```

  1. Create admin interface:

Option A: Manual route (React Router)

```tsx

// src/pages/Admin.tsx

import TinaCMS from 'tinacms'

export default function Admin() {

return

}

```

Option B: Direct HTML

```html

Tina CMS

```

  1. Use useTina hook for visual editing:

```tsx

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 (

{data.post.title}

{/ render body /}

)

}

```

  1. Set environment variables:

```env

# .env

VITE_TINA_CLIENT_ID=your_client_id

VITE_TINA_TOKEN=your_read_only_token

```

Template: See templates/vite-react/

---

3. Astro Setup

Steps:

  1. Use official starter (recommended):

```bash

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

```

Or initialize manually:

```bash

npx @tinacms/cli@latest init

```

  1. Update package.json scripts:

```json

{

"scripts": {

"dev": "tinacms dev -c \"astro dev\"",

"build": "tinacms build && astro build",

"preview": "astro preview"

}

}

```

  1. Configure Astro:

```javascript

// astro.config.mjs

import { defineConfig } from 'astro/config'

import react from '@astro/react'

export default defineConfig({

integrations: [react()] // Required for Tina admin

})

```

  1. Visual editing (experimental):

- Requires React components

- Use client:tinaDirective for interactive editing

- Full visual editing is experimental as of October 2025

  1. Set environment variables:

```env

# .env

PUBLIC_TINA_CLIENT_ID=your_client_id

TINA_TOKEN=your_read_only_token

```

Best For: Content-focused static sites, documentation, blogs

Template: See templates/astro/

---

4. Framework-Agnostic Setup

Applies to: Hugo, Jekyll, Eleventy, Gatsby, Remix, or any framework

Steps:

  1. Initialize TinaCMS:

```bash

npx @tinacms/cli@latest init

```

  1. Manually configure build scripts:

```json

{

"scripts": {

"dev": "tinacms dev -c \"\"",

"build": "tinacms build && "

}

}

```

  1. Admin interface:

- Automatically created at http://localhost:/admin/index.html

- Port depends on your framework

  1. Data fetching:

- No visual editing (sidebar only)

- Content edited through Git-backed interface

- Changes saved directly to files

  1. Set environment variables:

```env

TINA_CLIENT_ID=your_client_id

TINA_TOKEN=your_read_only_token

```

Limitations:

  • No visual editing (React-only feature)
  • Manual integration required
  • Sidebar-based editing only

---

Schema Modeling Best Practices

Define your content structure in tina/config.ts.

Basic Config Structure

```typescript

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

],

},

})

```

---

Collections

Collection = Content type + directory mapping

```typescript

{

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

}

```

Key Properties:

  • name: Internal identifier (alphanumeric + underscores only)
  • label: Human-readable name for admin interface
  • path: File path relative to project root
  • format: File extension (defaults to 'md')
  • fields: Content structure definition

---

Field Types Reference

| Type | Use Case | Example |

|------|----------|---------|

| string | Short text (single line) | Title, slug, author name |

| rich-text | Long formatted content | Blog body, page content |

| number | Numeric values | Price, quantity, rating |

| datetime | Date/time values | Published date, event time |

| boolean | True/false toggles | Draft status, featured flag |

| image | Image uploads | Hero image, thumbnail, avatar |

| reference | Link to another document | Author, category, related posts |

| object | Nested fields group | SEO metadata, social links |

Complete reference: See references/field-types-reference.md

---

Collection Templates

#### Blog Post Collection

```typescript

{

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}

}

}

```

Template: See templates/collections/blog-post.ts

---

#### Documentation Page Collection

```typescript

{

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}

}

}

}

```

Template: See templates/collections/doc-page.ts

---

#### Author Collection (Reference Target)

```typescript

{

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'

}

]

}

]

}

```

Template: See templates/collections/author.ts

---

#### Landing Page Collection (Multiple Templates)

```typescript

{

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'

}

]

}

]

}

]

}

```

When using templates: Documents must include _template field in frontmatter:

```yaml

---

_template: landing

title: My Landing Page

---

```

Template: See templates/collections/landing-page.ts

---

Common Errors & Solutions

1. ❌ ESbuild Compilation Errors

Error Message:

```

ERROR: Schema Not Successfully Built

ERROR: Config Not Successfully Executed

```

Causes:

  • Importing code with custom loaders (webpack, babel plugins, esbuild loaders)
  • Importing frontend-only code (uses window, DOM APIs, React hooks)
  • Importing entire component libraries instead of specific modules

Solution:

Import only what you need:

```typescript

// ❌ Bad - Imports entire component directory

import { HeroComponent } from '../components/'

// βœ… Good - Import specific file

import { HeroComponent } from '../components/blocks/hero'

```

Prevention Tips:

  • Keep tina/config.ts imports minimal
  • Only import type definitions and simple utilities
  • Avoid importing UI components directly
  • Create separate .schema.ts files if needed

Reference: See references/common-errors.md#esbuild

---

2. ❌ Module Resolution: "Could not resolve 'tinacms'"

Error Message:

```

Error: Could not resolve "tinacms"

```

Causes:

  • Corrupted or incomplete installation
  • Version mismatch between dependencies
  • Missing peer dependencies

Solution:

```bash

# 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

```

Prevention:

  • Use lockfiles (package-lock.json, pnpm-lock.yaml, yarn.lock)
  • Don't use --no-optional or --omit=optional flags
  • Ensure react and react-dom are installed (even for non-React frameworks)

---

3. ❌ Field Naming Constraints

Error Message:

```

Field name contains invalid characters

```

Cause:

  • TinaCMS field names can only contain: letters, numbers, underscores
  • Hyphens, spaces, special characters are NOT allowed

Solution:

```typescript

// ❌ 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'

}

```

Note: This is a breaking change from Forestry.io migration

---

4. ❌ Docker Binding Issues

Error:

  • TinaCMS admin not accessible from outside Docker container

Cause:

  • TinaCMS binds to 127.0.0.1 (localhost only) by default
  • Docker containers need 0.0.0.0 binding to accept external connections

Solution:

```bash

# 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 Example:

```yaml

services:

app:

build: .

ports:

- "3000:3000"

command: npm run dev # Which runs: tinacms dev -c "next dev --hostname 0.0.0.0"

```

---

5. ❌ Missing `_template` Key Error

Error Message:

```

GetCollection failed: Unable to fetch

template name was not provided

```

Cause:

  • Collection uses templates array (multiple schemas)
  • Document missing _template field in frontmatter
  • Migrating from templates to fields and documents not updated

Solution:

Option 1: Use fields instead (recommended for single template)

```typescript

{

name: 'post',

path: 'content/posts',

fields: [/ ... /] // No _template needed

}

```

Option 2: Ensure _template exists in frontmatter

```yaml

---

_template: article # ← Required when using templates array

title: My Post

---

```

Migration Script (if converting from templates to fields):

```bash

# Remove _template from all files in content/posts/

find content/posts -name "*.md" -exec sed -i '/_template:/d' {} +

```

---

6. ❌ Path Mismatch Issues

Error:

  • Files not appearing in Tina admin
  • "File not found" errors when saving
  • GraphQL queries return empty results

Cause:

  • path in collection config doesn't match actual file directory
  • Relative vs absolute path confusion
  • Trailing slash issues

Solution:

```typescript

// 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: [/ ... /]

}

```

Debugging:

  1. Run npx @tinacms/cli@latest audit to check paths
  2. Verify files exist in specified directory
  3. Check file extensions match format field

---

7. ❌ Build Script Ordering Problems

Error Message:

```

ERROR: Cannot find module '../tina/__generated__/client'

ERROR: Property 'queries' does not exist on type '{}'

```

Cause:

  • Framework build running before tinacms build
  • Tina types not generated before TypeScript compilation
  • CI/CD pipeline incorrect order

Solution:

```json

{

"scripts": {

"build": "tinacms build && next build" // βœ… Tina FIRST

// NOT: "build": "next build && tinacms build" // ❌ Wrong order

}

}

```

CI/CD Example (GitHub Actions):

```yaml

  • name: Build

run: |

npx @tinacms/cli@latest build # Generate types first

npm run build # Then build framework

```

Why This Matters:

  • tinacms build generates TypeScript types in tina/__generated__/
  • Framework build needs these types to compile successfully
  • Running in wrong order causes type errors

---

8. ❌ Failed Loading TinaCMS Assets

Error Message:

```

Failed to load resource: net::ERR_CONNECTION_REFUSED

http://localhost:4001/...

```

Causes:

  • Pushed development admin/index.html to production (loads assets from localhost)
  • Site served on subdirectory but basePath not configured

Solution:

For Production Deploys:

```json

{

"scripts": {

"build": "tinacms build && next build" // βœ… Always build

// NOT: "build": "tinacms dev" // ❌ Never dev in production

}

}

```

For Subdirectory Deployments:

```typescript

// tina/config.ts

export default defineConfig({

build: {

outputFolder: 'admin',

publicFolder: 'public',

basePath: 'your-subdirectory' // ← Set if site not at domain root

}

})

```

CI/CD Fix:

```yaml

# GitHub Actions / Vercel / Netlify

  • run: npx @tinacms/cli@latest build # Always use build, not dev

```

---

9. ❌ Reference Field 503 Service Unavailable

Error:

  • Reference field dropdown times out with 503 error
  • Admin interface becomes unresponsive when loading reference field

Cause:

  • Too many items in referenced collection (100s or 1000s)
  • No pagination support for reference fields currently

Solutions:

Option 1: Split collections

```typescript

// 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: [/ ... /]

}

```

Option 2: Use string field with validation

```typescript

// Instead of reference

{

type: 'string',

name: 'authorId',

label: 'Author ID',

ui: {

component: 'select',

options: ['author-1', 'author-2', 'author-3'] // Curated list

}

}

```

Option 3: Custom field component (advanced)

  • Implement pagination in custom component
  • See TinaCMS docs: https://tina.io/docs/extending-tina/custom-field-components/

---

Deployment Patterns

Choose the deployment approach that fits your needs.

Option 1: TinaCloud (Managed) - Easiest ⭐

Best For: Quick setup, free tier, managed infrastructure

Steps:

  1. Sign up at https://app.tina.io
  2. Create project, get Client ID and Read Only Token
  3. Set environment variables:

```env

NEXT_PUBLIC_TINA_CLIENT_ID=your_client_id

TINA_TOKEN=your_read_only_token

```

  1. Initialize backend:

```bash

npx @tinacms/cli@latest init backend

```

  1. Deploy to hosting provider (Vercel, Netlify, Cloudflare Pages)
  2. Set up GitHub integration in Tina dashboard

Pros:

  • βœ… Zero backend configuration
  • βœ… Automatic GraphQL API
  • βœ… Built-in authentication
  • βœ… Git integration handled automatically
  • βœ… Free tier generous (10k monthly requests)

Cons:

  • ❌ Paid service beyond free tier
  • ❌ Vendor dependency (content still in Git though)

Reference: See references/deployment-guide.md#tinacloud

---

Option 2: Self-Hosted on Cloudflare Workers πŸ”₯

Best For: Full control, Cloudflare ecosystem, edge deployment

Steps:

  1. Install dependencies:

```bash

npm install @tinacms/datalayer tinacms-authjs

```

  1. Initialize backend:

```bash

npx @tinacms/cli@latest init backend

```

  1. Create Workers endpoint:

```typescript

// 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)

}

}

```

  1. Update tina/config.ts:

```typescript

export default defineConfig({

contentApiUrlOverride: '/api/tina/gql', // Your Workers endpoint

// ... rest of config

})

```

  1. Configure wrangler.jsonc:

```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"

}

}

}

}

```

  1. Deploy:

```bash

npx wrangler deploy

```

Pros:

  • βœ… Full control over backend
  • βœ… Generous free tier (100k requests/day)
  • βœ… Global edge network (fast worldwide)
  • βœ… No vendor lock-in

Cons:

  • ❌ More setup complexity
  • ❌ Authentication configuration required
  • ❌ Cloudflare Workers knowledge needed

Complete Guide: See references/self-hosting-cloudflare.md

Template: See templates/cloudflare-worker-backend/

---

Option 3: Self-Hosted on Vercel Functions

Best For: Next.js projects, Vercel ecosystem

Steps:

  1. Install dependencies:

```bash

npm install @tinacms/datalayer tinacms-authjs

```

  1. Create API route:

```typescript

// 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

```

  1. Create vercel.json rewrites:

```json

{

"rewrites": [

{

"source": "/api/tina/:path*",

"destination": "/api/tina/backend"

}

]

}

```

  1. Update dev script:

```json

{

"scripts": {

"dev": "TINA_PUBLIC_IS_LOCAL=true tinacms dev -c \"next dev --port $PORT\""

}

}

```

  1. Set environment variables in Vercel dashboard:

```

NEXTAUTH_SECRET=your-secret

TINA_PUBLIC_IS_LOCAL=false

```

  1. Deploy:

```bash

vercel deploy

```

Pros:

  • βœ… Native Next.js integration
  • βœ… Simple Vercel deployment
  • βœ… Serverless (scales automatically)

Cons:

  • ❌ Vercel-specific
  • ❌ Function limitations (10s timeout, 50MB size)

Reference: See references/deployment-guide.md#vercel

---

Option 4: Self-Hosted on Netlify Functions

Steps:

  1. Install dependencies:

```bash

npm install express serverless-http @tinacms/datalayer tinacms-authjs

```

  1. Create function:

```typescript

// 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)

```

  1. Create netlify.toml:

```toml

[functions]

node_bundler = "esbuild"

[[redirects]]

from = "/api/tina/*"

to = "/.netlify/functions/tina"

status = 200

force = true

```

  1. Deploy:

```bash

netlify deploy --prod

```

Reference: See references/deployment-guide.md#netlify

---

Authentication Setup

Option 1: Local Development (Default)

Use for: Local development, no production deployment

```typescript

// tina/__generated__/databaseClient or backend config

const isLocal = process.env.TINA_PUBLIC_IS_LOCAL === 'true'

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

```

Environment Variable:

```env

TINA_PUBLIC_IS_LOCAL=true

```

Security: NO authentication - only use locally!

---

Option 2: Auth.js (Recommended for Self-Hosted)

Use for: Self-hosted with OAuth providers (GitHub, Discord, Google, etc.)

Install:

```bash

npm install next-auth tinacms-authjs

```

Configure:

```typescript

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,

})

```

Supported Providers: GitHub, Discord, Google, Twitter, Facebook, Email, etc.

Reference: https://next-auth.js.org/providers/

---

Option 3: TinaCloud Auth (Managed)

Use for: TinaCloud hosted service

```typescript

import { TinaCloudBackendAuthProvider } from '@tinacms/auth'

authProvider: TinaCloudBackendAuthProvider()

```

Setup:

  1. Sign up at https://app.tina.io
  2. Create project
  3. Manage users in dashboard
  4. Automatically handles authentication

---

Option 4: Custom Auth Provider

Use for: Existing auth system, custom requirements

```typescript

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 Usage

TinaCMS automatically generates a type-safe GraphQL client.

Querying Data

TinaCloud:

```typescript

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()

```

Self-Hosted:

```typescript

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

// Same API as TinaCloud client

const post = await client.queries.post({

relativePath: 'hello-world.md'

})

```

Visual Editing with useTina Hook

Next.js Example:

```tsx

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 (

{data.post.title}

{data.post.excerpt}

)

}

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

}

}

}

```

How It Works:

  • In production: useTina returns the initial data (no overhead)
  • In edit mode: useTina connects to GraphQL and updates in real-time
  • Changes appear immediately in preview

---

Additional Resources

Templates

  • templates/nextjs/ - Next.js App Router + Pages Router configs
  • templates/vite-react/ - Vite + React setup
  • templates/astro/ - Astro integration
  • templates/collections/ - Pre-built collection schemas
  • templates/cloudflare-worker-backend/ - Cloudflare Workers self-hosting

References

  • references/schema-patterns.md - Advanced schema modeling patterns
  • references/field-types-reference.md - Complete field type documentation
  • references/deployment-guide.md - Deployment guides for all platforms
  • references/self-hosting-cloudflare.md - Complete Cloudflare Workers guide
  • references/common-errors.md - Extended error troubleshooting
  • references/migration-guide.md - Migrating from Forestry.io

Scripts

  • scripts/init-nextjs.sh - Automated Next.js setup
  • scripts/init-vite-react.sh - Automated Vite + React setup
  • scripts/init-astro.sh - Automated Astro setup
  • scripts/check-versions.sh - Verify package versions

Official Documentation

  • Website: https://tina.io
  • Docs: https://tina.io/docs
  • GitHub: https://github.com/tinacms/tinacms
  • Discord: https://discord.gg/zumN63Ybpf

---

Token Efficiency

Estimated Savings: 65-70% (10,900 tokens saved)

Without Skill (~16,000 tokens):

  • Initial research and exploration: 3,000 tokens
  • Framework setup trial & error: 2,500 tokens
  • Schema modeling attempts: 2,000 tokens
  • Error troubleshooting: 4,000 tokens
  • Deployment configuration: 2,500 tokens
  • Authentication setup: 2,000 tokens

With Skill (~5,100 tokens):

  • Skill discovery: 100 tokens
  • Skill loading (SKILL.md): 3,000 tokens
  • Template selection: 500 tokens
  • Minor project-specific adjustments: 1,500 tokens

---

Errors Prevented

This skill prevents 9 common errors (100% prevention rate):

  1. βœ… ESbuild compilation errors (import issues)
  2. βœ… Module resolution problems
  3. βœ… Field naming constraint violations
  4. βœ… Docker binding issues
  5. βœ… Missing _template key errors
  6. βœ… Path mismatch problems
  7. βœ… Build script ordering failures
  8. βœ… Asset loading errors in production
  9. βœ… Reference field 503 timeouts

---

Quick Start Examples

Example 1: Blog with Next.js + TinaCloud

```bash

# 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

```

---

Example 2: Documentation Site with Astro

```bash

# 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

```

---

Example 3: Self-Hosted on Cloudflare Workers

```bash

# 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

```

---

Production Examples

  • TinaCMS Website: https://tina.io (dogfooding)
  • Astro Starter: https://github.com/tinacms/tina-astro-starter
  • Next.js Starter: https://github.com/tinacms/tina-starter-alpaca

---

Support

Issues? Check references/common-errors.md first

Still Stuck?

  • Discord: https://discord.gg/zumN63Ybpf
  • GitHub Issues: https://github.com/tinacms/tinacms/issues
  • Official Docs: https://tina.io/docs

---

Last Updated: 2025-10-24

Skill Version: 1.0.0

TinaCMS Version: 2.9.0

CLI Version: 1.11.0