Forestry (TinaCMS)
Git-based headless CMS. Developer-first CMS optimized for JAMstack workflows.
CMS
Forestry (TinaCMS)
Overview
Forestry was a beloved Git-based headless CMS among developers, but it ended its service on April 22, 2023, with TinaCMS being developed as its successor.
Details
Forestry.io was supported by many developers as a Git-based headless CMS optimized for JAMstack workflows. It provided an intuitive interface for managing Markdown and YAML files through direct integration with Git repositories. However, the Forestry team decided to focus on developing TinaCMS as their next-generation vision and ended Forestry's service in April 2023.
TinaCMS inherits the best aspects of Forestry while offering more advanced features. Particularly noteworthy are its visual editing capabilities and type safety through TypeScript. Adopting a modern React-based architecture, it achieves seamless integration with static site generators like Next.js and Gatsby. Through integration with GitHub and GitLab, it maintains developer-friendly workflows while providing a visual editor that's accessible to non-technical users.
Pros and Cons
Pros
- Git-based workflow: Integrated version control and development flow
- Visual editing: Real-time preview and click-to-edit functionality
- Type safety: Complete type support through TypeScript
- Open source: Customizable without vendor lock-in
- Modern developer experience: Latest tech stack based on React/TypeScript
- Flexible authentication: Supports both local development and cloud authentication
- GraphQL API: Efficient data fetching
Cons
- Migration required from Forestry: Existing Forestry users need to perform migration work
- Learning curve: Need to learn new concepts and APIs
- React-specific: Limited integration with other frameworks
- Complex initial setup: Technical knowledge required for environment setup
- Ecosystem size: Still developing compared to Forestry
Main Links
- TinaCMS Official Site
- TinaCMS Documentation
- TinaCMS GitHub
- Forestry to TinaCMS Migration Guide
- TinaCMS Community
- TinaCMS Starter Templates
Usage Examples
Initializing TinaCMS Project (Forestry Migration)
# Create new project
npx create-tina-app@latest my-site
# Migrate existing Forestry project
npx @tinacms/cli init
# Answer "y" to "Migrate your .forestry folder?"
# Start development server
npm run dev
Schema Definition (TypeScript)
// .tina/schema.ts
import { defineSchema } from "@tinacms/cli";
export default defineSchema({
collections: [
{
label: "Blog Posts",
name: "post",
path: "content/posts",
fields: [
{
type: "string",
label: "Title",
name: "title",
required: true,
},
{
type: "string",
label: "Body",
name: "body",
isBody: true,
ui: {
component: "textarea",
},
},
{
type: "reference",
label: "Author",
name: "author",
collections: ["author"],
},
],
},
],
});
Database Configuration (Git Provider)
// tina/database.ts
import { createDatabase, createLocalDatabase } from "@tinacms/datalayer";
import { GitHubProvider } from "tinacms-gitprovider-github";
import { MongodbLevel } from "mongodb-level";
const isLocal = process.env.TINA_PUBLIC_IS_LOCAL === "true";
export default isLocal
? createLocalDatabase()
: 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,
}),
databaseAdapter: new MongodbLevel({
collectionName: "tinacms",
dbName: "tinacms",
mongoUri: process.env.MONGODB_URI,
}),
});
Next.js Integration
// pages/_app.tsx
import { TinaEditProvider } from 'tinacms/dist/edit-state'
import TinaCMS from 'tinacms'
function MyApp({ Component, pageProps }) {
return (
<TinaEditProvider
editMode={
<TinaCMS
apiURL={process.env.NEXT_PUBLIC_TINA_API_URL}
{...pageProps}
>
{(livePageProps) => <Component {...livePageProps} />}
</TinaCMS>
}
>
<Component {...pageProps} />
</TinaEditProvider>
)
}
export default MyApp
Backend Authentication Setup
// pages/api/tina/[...routes].ts
import { TinaNodeBackend, LocalBackendAuthProvider } from "@tinacms/datalayer";
import { TinaAuthJSOptions, AuthJsBackendAuthProvider } 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: databaseClient,
secret: process.env.NEXTAUTH_SECRET,
}),
}),
databaseClient,
});
export default handler;
Visual Editing Implementation
// components/VisualEditor.tsx
import { useTina } from 'tinacms/dist/react'
import { useVisualEditing } from '@tinacms/vercel-previews'
export const BlogPost = (props) => {
const { data: tinaData } = useTina(props)
// Enable visual editing
const data = useVisualEditing({
data: tinaData,
query: props.query,
variables: props.variables,
enabled: true,
stringEncoding: true, // Automatically add metadata to strings
})
return (
<article>
<h1>{data.post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: data.post.body }} />
</article>
)
}