Loading...
Loading...
Build content-heavy sites with Git-backed TinaCMS. Provides visual editing for blogs, documentation, and marketing sites. Supports Next.js, Vite+React, and Astro with TinaCloud or Node.js self-hosting. Prevents 10 documented errors. Use when setting up CMS with non-technical editors or troubleshooting ESbuild compilation, module resolution, package manager compatibility, edge runtime limitations, or media upload timeouts.
npx skill4agent add jezweb/claude-skills tinacms# Install pnpm (if needed)
npm install -g pnpm
# Initialize TinaCMS
npx @tinacms/cli@latest init
# Install dependencies with pnpm
pnpm install
# Update package.json scripts
{
"dev": "tinacms dev -c \"next dev\"",
"build": "tinacms build && next build"
}
# Set environment variables
NEXT_PUBLIC_TINA_CLIENT_ID=your_client_id
TINA_TOKEN=your_read_only_token
# Start dev server
pnpm run dev
# Access admin interface
http://localhost:3000/admin/index.html{
"dependencies": {
"tinacms": "3.3.1", // NOT "^3.3.1"
"@tinacms/cli": "2.1.1"
}
}import { useTina } from 'tinacms/dist/react'
import { client } from '../../tina/__generated__/client'
export default function BlogPost(props) {
const { data } = useTina({
query: props.query,
variables: props.variables,
data: props.data
})
return <article><h1>{data.post.title}</h1></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
}
}
}app/admin/[[...index]]/page.tsxpages/admin/[[...index]].tsximport { defineConfig } from 'tinacms'
export default defineConfig({
branch: process.env.GITHUB_BRANCH || 'main',
clientId: process.env.NEXT_PUBLIC_TINA_CLIENT_ID,
token: process.env.TINA_TOKEN,
build: {
outputFolder: 'admin',
publicFolder: 'public',
},
schema: {
collections: [/* ... */],
},
}){
name: 'post', // Alphanumeric + underscores only
label: 'Blog Posts',
path: 'content/posts', // No trailing slash
format: 'mdx',
fields: [
{
type: 'string',
name: 'title',
label: 'Title',
isTitle: true,
required: true
},
{
type: 'rich-text',
name: 'body',
label: 'Body',
isBody: true
}
]
}stringrich-textnumberdatetimebooleanimagereferenceobjectbio: stringbio: rich-text// Example: Reference field referencing multiple collections
{
type: 'reference',
name: 'contributor',
collections: ['author', 'editor'] // Ensure shared fields have same type
}ERROR: Schema Not Successfully Built
ERROR: Config Not Successfully Executedwindow// ❌ Bad - Imports entire component directory
import { HeroComponent } from '../components/'
// ✅ Good - Import specific file
import { HeroComponent } from '../components/blocks/hero'tina/config.ts.schema.tsreferences/common-errors.md#esbuildError: 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 installpackage-lock.jsonpnpm-lock.yamlyarn.lock--no-optional--omit=optionalreactreact-domField name contains invalid characters// ❌ 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'
}127.0.0.10.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"services:
app:
build: .
ports:
- "3000:3000"
command: npm run dev # Which runs: tinacms dev -c "next dev --hostname 0.0.0.0"_templateGetCollection failed: Unable to fetch
template name was not providedtemplates_templatetemplatesfieldsfields{
name: 'post',
path: 'content/posts',
fields: [/* ... */] // No _template needed
}_template---
_template: article # ← Required when using templates array
title: My Post
---# Remove _template from all files in content/posts/
find content/posts -name "*.md" -exec sed -i '/_template:/d' {} +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: [/* ... */]
}npx @tinacms/cli@latest auditformatERROR: Cannot find module '../tina/__generated__/client'
ERROR: Property 'queries' does not exist on type '{}'tinacms build{
"scripts": {
"build": "tinacms build && next build" // ✅ Tina FIRST
// NOT: "build": "next build && tinacms build" // ❌ Wrong order
}
}- name: Build
run: |
npx @tinacms/cli@latest build # Generate types first
npm run build # Then build frameworktinacms buildtina/__generated__/Failed to load resource: net::ERR_CONNECTION_REFUSED
http://localhost:4001/...admin/index.htmlbasePath{
"scripts": {
"build": "tinacms build && next build" // ✅ Always build
// NOT: "build": "tinacms dev" // ❌ Never dev in production
}
}⚠️ Sub-path Deployment Limitation: TinaCMS has known issues loading assets correctly when deployed to a sub-path (e.g.,instead ofexample.com/cms/admin). This is a limitation even withexample.com/adminconfiguration.basePathWorkaround: Deploy TinaCMS admin at root path () or use reverse proxy rewrite rules./adminSource: Community-sourced
// tina/config.ts
export default defineConfig({
build: {
outputFolder: 'admin',
publicFolder: 'public',
basePath: 'your-subdirectory' // ← May have asset loading issues on sub-paths
}
})# GitHub Actions / Vercel / Netlify
- run: npx @tinacms/cli@latest build # Always use build, not dev// 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: [/* ... */]
}// Instead of reference
{
type: 'string',
name: 'authorId',
label: 'Author ID',
ui: {
component: 'select',
options: ['author-1', 'author-2', 'author-3'] // Curated list
}
}Upload failed
Error uploading imageNEXT_PUBLIC_TINA_CLIENT_IDTINA_TOKEN⚠️ Edge Runtime Limitation: Self-hosted TinaCMS does NOT work in Edge Runtime environments (Cloudflare Workers, Vercel Edge Functions) due to Node.js dependencies inand@tinacms/datalayer. Use TinaCloud (managed service) for edge deployments.@tinacms/graphqlSource: GitHub Issue #4363 (labeled "wontfix")
⚠️ Self-Hosted Examples May Be Outdated: Official self-hosted examples in the TinaCMS repository are acknowledged by the team as "quite out of date". Always cross-reference with latest documentation instead of relying solely on example repos.Source: GitHub Issue #6365
pnpm install @tinacms/datalayer tinacms-authjs
npx @tinacms/cli@latest init backendimport { 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'
// This ONLY works in Node.js runtime, NOT edge runtime
const handler = TinaNodeBackend({
authProvider: isLocal
? LocalBackendAuthProvider()
: AuthJsBackendAuthProvider({
authOptions: TinaAuthJSOptions({
databaseClient,
secret: process.env.NEXTAUTH_SECRET,
}),
}),
databaseClient,
})