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
-
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
-
Visual/Contextual Editing
- Side-by-side editing experience
- Live preview of changes as you type
- WYSIWYG-like editing for Markdown content
-
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
- Define content structure in code (
-
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)
-
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:
-
Building Content-Heavy Sites
- Blogs and personal websites
- Documentation sites
- Marketing websites
- Portfolio sites
-
Non-Technical Editors Need Access
- Content teams without coding knowledge
- Marketing teams managing pages
- Authors writing blog posts
-
Git-Based Workflow Desired
- Want content versioning through Git
- Need content review through pull requests
- Prefer content in repository with code
-
Visual Editing Required
- Editors want to see changes live
- WYSIWYG experience preferred
- Side-by-side editing workflow
❌ Don't Use TinaCMS When:
-
Real-Time Collaboration Needed
- Multiple users editing simultaneously (Google Docs-style)
- Use Sanity, Contentful, or Firebase instead
-
Highly Dynamic Data
- E-commerce product catalogs with frequent inventory changes
- Real-time dashboards
- Use traditional databases (D1, PostgreSQL) instead
-
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:
-
Initialize TinaCMS:
npx @tinacms/cli@latest init- When prompted for public assets directory, enter
public
- When prompted for public assets directory, enter
-
Update package.json scripts:
{ "scripts": { "dev": "tinacms dev -c \"next dev\"", "build": "tinacms build && next build", "start": "tinacms build && next start" } } -
Set environment variables:
# .env.local NEXT_PUBLIC_TINA_CLIENT_ID=your_client_id TINA_TOKEN=your_read_only_token -
Start development server:
npm run dev -
Access admin interface:
http://localhost:3000/admin/index.html
Key Files Created:
tina/config.ts- Schema configurationapp/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]].tsxinstead of app directory
Data Fetching Pattern:
// 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' }
}
Template: See templates/nextjs/tina-config-pages-router.ts
2. Vite + React Setup
Steps:
-
Install dependencies:
npm install react@^19 react-dom@^19 tinacms -
Initialize TinaCMS:
npx @tinacms/cli@latest init- Set public assets directory to
public
- Set public assets directory to
-
Update vite.config.ts:
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { port: 3000 // TinaCMS default } }) -
Update package.json scripts:
{ "scripts": { "dev": "tinacms dev -c \"vite\"", "build": "tinacms build && vite build", "preview": "vite preview" } } -
Create admin interface:
Option A: Manual route (React Router)
// src/pages/Admin.tsx import TinaCMS from 'tinacms' export default function Admin() { return <div id="tina-admin" /> }Option B: Direct HTML
<!-- public/admin/index.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> -
Use useTina hook for visual editing:
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> ) } -
Set environment variables:
# .env VITE_TINA_CLIENT_ID=your_client_id VITE_TINA_TOKEN=your_read_only_token
Template: See templates/vite-react/
3. Astro Setup
Steps:
-
Use official starter (recommended):
npx create-tina-app@latest --template tina-astro-starterOr initialize manually:
npx @tinacms/cli@latest init -
Update package.json scripts:
{ "scripts": { "dev": "tinacms dev -c \"astro dev\"", "build": "tinacms build && astro build", "preview": "astro preview" } } -
Configure Astro:
// astro.config.mjs import { defineConfig } from 'astro/config' import react from '@astro/react' export default defineConfig({ integrations: [react()] // Required for Tina admin }) -
Visual editing (experimental):
- Requires React components
- Use
client:tinaDirectivefor interactive editing - Full visual editing is experimental as of October 2025
-
Set environment variables:
# .env PUBLI