Netlify CMS (Decap CMS)

Now independent as Decap CMS. Git-based CMS for JAMstack. Strong integration with static site generators.

CMSHeadlessGit-basedJAMstackJavaScriptReactOpen Source
License
MIT
Language
JavaScript/React
Pricing
完全無料
Official Site
Visit Official Site

CMS

Netlify CMS (Decap CMS)

Overview

Netlify CMS was rebranded as Decap CMS in February 2023 and is now maintained and developed as an open source project by PM TechHub as a Git-based headless CMS.

Details

Decap CMS (formerly Netlify CMS) is an open source content management system for Git workflows that provides editors with a friendly UI and intuitive workflows. Content is stored in your Git repository alongside your code, enabling version control, multi-channel publishing, and direct content updates in Git.

At its core, it's a React application that acts as a wrapper for Git workflows, using the GitHub, GitLab, or Bitbucket API. Specialized for JAMstack architecture, it's compatible with most static site generators including Hugo, Jekyll, Gatsby, and Next.js. With simple setup, it's widely used for small to medium-sized projects.

Pros and Cons

Pros

  • Completely free: Open source with no licensing costs
  • Git integration: Unified version control and workflow
  • Easy setup: Start with just two files
  • Static site specialized: Perfect integration with SSGs
  • Platform agnostic: Can be hosted anywhere
  • Rich SSG support: Covers major static site generators
  • OAuth authentication: Authentication with GitHub/GitLab accounts

Cons

  • Basic features: Limited advanced functionality
  • No dynamic functionality: Not suitable for real-time content
  • UI limitations: Low customizability
  • Technical knowledge required: Basic Git and YAML knowledge essential
  • Lack of enterprise features: Insufficient for large teams
  • Media management constraints: Basic image management

Main Links

Usage Examples

Basic Setup

<!-- /admin/index.html -->
<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Content Manager</title>
</head>
<body>
  <!-- Load CMS -->
  <script src="https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"></script>
</body>
</html>

Configuration File (config.yml)

# /admin/config.yml
backend:
  name: git-gateway # GitHub/GitLab/Bitbucket also available
  branch: main # Branch name

# Media folder settings
media_folder: "static/images/uploads"
public_folder: "/images/uploads"

# Collection definitions
collections:
  - name: "blog" # Used in URL path
    label: "Blog" # UI display name
    folder: "content/blog" # File save location
    create: true # Allow new creation
    slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
    fields:
      - {label: "Title", name: "title", widget: "string"}
      - {label: "Publish Date", name: "date", widget: "datetime"}
      - {label: "Featured Image", name: "thumbnail", widget: "image"}
      - {label: "Body", name: "body", widget: "markdown"}

Workflow Configuration

# Enable editorial workflow
publish_mode: editorial_workflow

collections:
  - name: "pages"
    label: "Pages"
    folder: "content/pages"
    create: true
    # Define workflow status
    fields:
      - {label: "Title", name: "title", widget: "string"}
      - {label: "Draft", name: "draft", widget: "boolean", default: true}
      - {label: "Content", name: "body", widget: "markdown"}

Next.js Integration

// pages/admin.js
import dynamic from 'next/dynamic'

// Load CMS only on client side
const AdminWithNoSSR = dynamic(
  () => import('../components/AdminPanel'),
  { ssr: false }
)

export default function Admin() {
  return <AdminWithNoSSR />
}

// components/AdminPanel.js
import { useEffect } from 'react'

export default function AdminPanel() {
  useEffect(() => {
    ;(async () => {
      const CMS = (await import('decap-cms-app')).default
      CMS.init()
    })()
  }, [])

  return <div id="nc-root" />
}

Custom Widgets

// Custom preview component
CMS.registerPreviewTemplate('blog', BlogPostPreview)

// Register custom widget
CMS.registerWidget(
  'color',
  ColorControl,
  ColorPreview
)

// Custom editor component
CMS.registerEditorComponent({
  id: 'youtube',
  label: 'YouTube',
  fields: [{name: 'url', label: 'YouTube URL', widget: 'string'}],
  pattern: /^youtube (\S+)$/,
  fromBlock: function(match) {
    return {
      url: match[1]
    }
  },
  toBlock: function(obj) {
    return `youtube ${obj.url}`
  },
  toPreview: function(obj) {
    return `<iframe src="${obj.url}" />`
  }
})

Local Development Backend

# Start local backend
npx decap-server

# Configure local backend in config.yml
backend:
  name: proxy
  proxy_url: http://localhost:8081/api/v1
  branch: main # Local branch