TinaCMS

React-based Git headless CMS. Next-generation CMS that balances visual editing with code management.

CMSHeadlessGit-basedTypeScriptReactGraphQLVisual Editing
License
Apache 2.0
Language
TypeScript/React
Pricing
開発者プラン無料
Official Site
Visit Official Site

CMS

TinaCMS

Overview

TinaCMS is a React-based Git-based headless CMS that balances visual editing with code management, representing the next generation of developer-first CMS.

Details

TinaCMS is a next-generation Git-based CMS developed by the Forestry.io team, combining visual editing capabilities with developer-friendly workflows in an innovative CMS. It fully leverages TypeScript and React to provide type safety and a modern development experience.

What sets it apart is the visual editing through "click-to-edit" functionality. Users can directly click and edit content while viewing the website, making it intuitive even for non-technical users. At the same time, by adopting a Git-based workflow, developers can maintain their familiar version control processes.

It provides a GraphQL API and covers all features necessary for modern development, including MDX support, real-time preview, and branch-based editing. Integration with Next.js and Gatsby is particularly excellent, making it ideal for JAMstack projects.

Pros and Cons

Pros

  • Visual editing: Intuitive content editing with click-to-edit
  • Type safety: Full TypeScript support prevents errors in advance
  • Modern DX: Latest development experience based on React/TypeScript
  • Git integration: Seamless integration with version control and CI/CD
  • GraphQL API: Efficient data fetching
  • MDX support: Enables rich content creation
  • Open source: Free customization and extension

Cons

  • React-specific: Difficult to use with other frameworks
  • High learning curve: Requires knowledge of TypeScript/React/GraphQL
  • New product: Ecosystem still developing
  • Complex configuration: Initial setup takes time
  • Resource usage: Visual editing features can be resource-intensive

Main Links

Usage Examples

Project Initialization

# Create new project
npx create-tina-app@latest

# Add to existing project
npx @tinacms/cli init

# Start development server
npm run dev

# TinaCMS admin panel: http://localhost:3000/admin

Schema Definition (Type-safe)

// .tina/schema.ts
import { defineSchema, defineConfig } from "@tinacms/cli";

export const schema = defineSchema({
  collections: [
    {
      label: "Articles",
      name: "article",
      path: "content/articles",
      format: "mdx",
      fields: [
        {
          type: "string",
          label: "Title",
          name: "title",
          required: true,
        },
        {
          type: "datetime",
          label: "Published Date",
          name: "publishedAt",
        },
        {
          type: "reference",
          label: "Author",
          name: "author",
          collections: ["author"],
        },
        {
          type: "rich-text",
          label: "Content",
          name: "body",
          isBody: true,
          templates: [
            {
              name: "CodeBlock",
              label: "Code Block",
              fields: [
                {
                  name: "language",
                  label: "Language",
                  type: "string",
                },
                {
                  name: "code",
                  label: "Code",
                  type: "string",
                  ui: {
                    component: "textarea",
                  },
                },
              ],
            },
          ],
        },
      ],
    },
  ],
});

export default defineConfig({
  schema,
  branch: "main",
  clientId: process.env.TINA_CLIENT_ID!,
  token: process.env.TINA_TOKEN!,
  build: {
    publicFolder: "public",
    outputFolder: "admin",
  },
});

Next.js Integration and Visual Editing

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

export default function Post(props: {
  data: any
  variables: any
  query: string
}) {
  // Enable visual editing
  const { data } = useTina({
    query: props.query,
    variables: props.variables,
    data: props.data,
  })

  return (
    <article>
      <h1>{data.article.title}</h1>
      <TinaMarkdown content={data.article.body} />
    </article>
  )
}

export const getStaticProps = async ({ params }: any) => {
  const { data, query, variables } = await client.queries.article({
    relativePath: params.slug + '.mdx',
  })

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

GraphQL Query Execution

// Custom GraphQL query
import { client } from './.tina/__generated__/client'

export async function getArticles() {
  const articlesResponse = await client.queries.articleConnection()
  
  return articlesResponse.data.articleConnection.edges?.map(
    (article) => ({
      title: article.node?.title,
      slug: article.node?._sys.filename,
      publishedAt: article.node?.publishedAt,
    })
  )
}

Custom Field Component

// Register custom field
import { wrapFieldsWithMeta } from 'tinacms'

const ColorPickerField = wrapFieldsWithMeta((props) => {
  const { field, input } = props
  
  return (
    <div>
      <label htmlFor={input.name}>{field.label}</label>
      <input
        type="color"
        id={input.name}
        value={input.value}
        onChange={(e) => input.onChange(e.target.value)}
      />
    </div>
  )
})

// Register with CMS
export const tinaConfig = defineConfig({
  // ...
  cmsCallback: (cms) => {
    cms.fields.add({
      name: 'color',
      Component: ColorPickerField,
    })
  },
})

Switching Between Local Development and Cloud

// tina/database.ts
import { createDatabase, createLocalDatabase } from "@tinacms/datalayer"
import { GitHubProvider } from "tinacms-gitprovider-github"

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

export default isLocal
  ? createLocalDatabase() // Local development
  : createDatabase({
      gitProvider: new GitHubProvider({
        branch: process.env.GITHUB_BRANCH!,
        owner: process.env.GITHUB_OWNER!,
        repo: process.env.GITHUB_REPO!,
        token: process.env.GITHUB_PERSONAL_ACCESS_TOKEN!,
      }),
    })