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
- 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:
``bash npx @tinacms/cli@latest init ``
- When prompted for public assets directory, enter
public
- Update package.json scripts:
``json { "scripts": { "dev": "tinacms dev -c \"next dev\"", "build": "tinacms build && next build", "start": "tinacms build && next start" } } ``
- Set environment variables:
``env # .env.local NEXT_PUBLIC_TINA_CLIENT_ID=your_client_id TINA_TOKEN=your_read_only_token ``
- Start development server:
``bash 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:
``bash npm install react@^19 react-dom@^19 tinacms ``
- Initialize TinaCMS:
``bash npx @tinacms/cli@latest init ``
- Set public assets directory to
public
- 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 } }) ```
- Update package.json scripts:
``json { "scripts": { "dev": "tinacms dev -c \"vite\"", "build": "tinacms build && vite build", "preview": "vite preview" } } ``
- Create admin interface:
Option A: Manual route (React Router) ```tsx // src/pages/Admin.tsx import TinaCMS from 'tinacms'
export default function Admin() { return <div id="tina-admin" /> } ```
Option B: Direct HTML ``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:
```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 ( <article> <h1>{data.post.title}</h1> <div>{/ render body /}</div> </article> ) } ```
- 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:
- Use official starter (recommended):
``bash npx create-tina-app@latest --template tina-astro-starter ``
Or initialize manually: ``bash npx @tinacms/cli@latest init ``
- Update package.json scripts:
``json { "scripts": { "dev": "tinacms dev -c \"astro dev\"", "build": "tinacms build && astro build", "preview": "astro preview" } } ``
- 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 }) ```
- Visual editing (experimental):
- Requires React components
- Use
client:tinaDirectivefor interactive editing - Full visual editing is experimental as of October 2025
- Set environment variables:
```env # .env PUBLI