Salin dan Bagikan
Cara Integrasi Netlify CMS dengan Hugo untuk Headless CMS - Tutorial lengkap integrasi Netlify CMS dengan Hugo untuk headless CMS. Setup admin panel, Git-based …

Cara Integrasi Netlify CMS dengan Hugo untuk Headless CMS

Cara Integrasi Netlify CMS dengan Hugo untuk Headless CMS

Netlify CMS (sekarang Decap CMS) adalah open-source headless CMS yang menggunakan Git sebagai backend. Integrasi dengan Hugo memberikan workflow content management yang modern dan efisien.

Apa itu Netlify CMS?

Fitur Utama

  • Git-based: Semua content tersimpan di Git repository
  • Editorial Workflow: Draft, review, publish workflow
  • Media Library: Upload dan manage images
  • Real-time Preview: Preview content sebelum publish
  • Open Source: Gratis dan self-hostable

Arsitektur

Browser (Netlify CMS Admin)
Git Gateway (Authentication)
Git Repository (GitHub/GitLab/Bitbucket)
Hugo Build → Static Site
CDN (Netlify/Cloudflare/Vercel)

Setup Netlify CMS dengan Hugo

Step 1: Struktur Project

my-hugo-site/
├── content/
├── static/
│   └── admin/              # Netlify CMS admin files
│       ├── index.html      # Admin interface
│       └── config.yml      # CMS configuration
├── layouts/
└── hugo.toml

Step 2: Buat Admin Interface

File static/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>
  <script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
</head>
<body>
  <script src="https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"></script>
  <script>
    if (window.netlifyIdentity) {
      window.netlifyIdentity.on("init", user => {
        if (!user) {
          window.netlifyIdentity.on("login", () => {
            document.location.href = "/admin/";
          });
        }
      });
    }
  </script>
</body>
</html>

Step 3: Konfigurasi CMS (config.yml)

File static/admin/config.yml:

backend:
  name: git-gateway
  branch: main
  repo: username/repository-name

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

# Collections
collections:
  # Blog Posts
  - name: "posts"
    label: "Blog Posts"
    folder: "content/posts"
    create: true
    slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
    fields:
      - { label: "Title", name: "title", widget: "string" }
      - { label: "Publish Date", name: "date", widget: "datetime" }
      - { label: "Draft", name: "draft", widget: "boolean", default: false }
      - { label: "Description", name: "description", widget: "text" }
      - { label: "Featured Image", name: "image", widget: "image", required: false }
      - { label: "Categories", name: "categories", widget: "list", required: false }
      - { label: "Tags", name: "tags", widget: "list", required: false }
      - { label: "Body", name: "body", widget: "markdown" }

  # Pages
  - name: "pages"
    label: "Pages"
    folder: "content"
    create: true
    slug: "{{slug}}"
    fields:
      - { label: "Title", name: "title", widget: "string" }
      - { label: "Publish Date", name: "date", widget: "datetime" }
      - { label: "Draft", name: "draft", widget: "boolean", default: false }
      - { label: "Body", name: "body", widget: "markdown" }

# Editorial Workflow
publish_mode: editorial_workflow

# Display URLs
show_preview_links: true

Step 4: Setup Git Gateway

Di Netlify Dashboard:

  1. Buka Site SettingsIdentity
  2. Enable Identity → Click “Enable Identity”
  3. ServicesGit Gateway → Click “Enable Git Gateway”
  4. Registration → Set ke “Open” (untuk testing) atau “Invite only”

Step 5: Identity Widget Setup

Tambahkan widget ke homepage (layouts/partials/head.html):

<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
<script>
  if (window.netlifyIdentity) {
    window.netlifyIdentity.on("init", user => {
      if (!user) {
        window.netlifyIdentity.on("login", () => {
          document.location.href = "/admin/";
        });
      }
    });
  }
</script>

Advanced CMS Configuration

Widget Types

collections:
  - name: "posts"
    fields:
      # Basic widgets
      - { label: "Title", name: "title", widget: "string" }
      - { label: "Body", name: "body", widget: "markdown" }
      - { label: "Publish Date", name: "date", widget: "datetime" }
      - { label: "Image", name: "image", widget: "image" }
      - { label: "File", name: "file", widget: "file" }
      - { label: "Draft", name: "draft", widget: "boolean" }
      
      # Advanced widgets
      - { label: "Rating", name: "rating", widget: "number", value_type: "int", min: 1, max: 5 }
      - { label: "Layout", name: "layout", widget: "select", options: ["default", "full-width", "sidebar"] }
      - { label: "Color", name: "color", widget: "color" }
      - { label: "Content", name: "content", widget: "text" }
      - { label: "Code", name: "code", widget: "code" }
      
      # Lists
      - { label: "Categories", name: "categories", widget: "list" }
      - { label: "Tags", name: "tags", widget: "list", allow_add: true }
      
      # Object
      - label: "Author"
        name: "author"
        widget: "object"
        fields:
          - { label: "Name", name: "name", widget: "string" }
          - { label: "Email", name: "email", widget: "string" }
          - { label: "Bio", name: "bio", widget: "text" }

Folder Collections

collections:
  # Single folder untuk posts
  - name: "posts"
    label: "Blog Posts"
    folder: "content/posts"
    create: true
    
  # Nested folders (misal: docs dengan struktur folder)
  - name: "docs"
    label: "Documentation"
    folder: "content/docs"
    create: true
    nested:
      depth: 3  # Maksimum depth
      summary: '{{title}}'
    fields:
      - { label: "Title", name: "title", widget: "string" }
      - { label: "Body", name: "body", widget: "markdown" }

File Collections

collections:
  # Untuk single files seperti settings
  - name: "settings"
    label: "Site Settings"
    files:
      - label: "General Settings"
        name: "general"
        file: "config/_default/params.yaml"
        fields:
          - { label: "Site Title", name: "title", widget: "string" }
          - { label: "Description", name: "description", widget: "text" }
          - { label: "Author", name: "author", widget: "string" }
          
      - label: "Social Links"
        name: "social"
        file: "config/_default/social.yaml"
        fields:
          - label: "Social Links"
            name: "links"
            widget: "list"
            fields:
              - { label: "Platform", name: "platform", widget: "string" }
              - { label: "URL", name: "url", widget: "string" }

Editorial Workflow

Draft → Review → Publish

# config.yml
publish_mode: editorial_workflow

Workflow:

  1. Draft: Author membuat content baru
  2. In Review: Editor review content
  3. Ready: Approved dan siap publish

Hugo akan build:

  • Draft: Hanya preview URL
  • Published: Live di production site
show_preview_links: true

Setiap draft akan mendapat preview URL unique untuk review.

Custom Previews

Register Preview Template

<!-- static/admin/index.html -->
<script>
  CMS.registerPreviewStyle("/admin/preview.css");
  
  CMS.registerPreviewTemplate("posts", createClass({
    render: function() {
      const entry = this.props.entry;
      return h('div', {},
        h('h1', {}, entry.getIn(['data', 'title'])),
        h('img', {src: entry.getIn(['data', 'image'])}),
        h('div', {"className": "content"}, this.props.widgetFor('body'))
      );
    }
  }));
</script>

Preview Styles

File static/admin/preview.css:

/* Preview styles untuk match Hugo theme */
body {
  font-family: -apple-system, system-ui, sans-serif;
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

img {
  max-width: 100%;
  height: auto;
}

Media Library

Cloudinary Integration

# config.yml
media_library:
  name: cloudinary
  config:
    cloud_name: your-cloud-name
    api_key: your-api-key
    default_transformations:
      - fetch_format: auto
        quality: auto
        width: 800
        crop: scale

Uploadcare Integration

media_library:
  name: uploadcare
  config:
    publicKey: your-public-key

Custom Media Library

media_library:
  name: custom
  config:
    media_folder: "static/images"
    public_folder: "/images"

Authentication Options

1. Netlify Identity (Default)

backend:
  name: git-gateway

2. GitHub Backend

backend:
  name: github
  repo: owner/repo
  branch: main
  base_url: https://api.netlify.com
  auth_endpoint: auth

3. GitLab Backend

backend:
  name: gitlab
  repo: owner/repo
  branch: main
  auth_type: pkce

Deployment Integration

Netlify

  1. Connect repository ke Netlify
  2. Enable Identity dan Git Gateway
  3. CMS otomatis ter-deploy dengan Hugo site

GitHub Pages + Actions

# .github/workflows/cms.yml
name: Deploy Hugo with CMS
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: peaceiris/actions-hugo@v2
      - run: hugo --minify
      - uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./public

Customization

Custom Widgets

// Custom color widget
CMS.registerWidget(
  'color',           // Widget name
  ColorControl,      // React component
  ColorPreview       // Preview component
);

Custom Icons

# config.yml
collections:
  - name: "posts"
    label: "Posts"
    icon: "news"  # Material icon name

Custom Slug Format

slug:
  encoding: "unicode"
  clean_accents: false
  sanitize_replacement: "-"

Troubleshooting

Issue: “Git Gateway Error”

Solusi:

  1. Check Identity ter-enable
  2. Git Gateway sudah diaktifkan
  3. Repository permissions benar

Issue: “Failed to Persist”

Solusi:

backend:
  name: git-gateway
  squash_merges: true  # Untuk menghindari conflict

Issue: “CORS Error”

Solusi:
Tambahkan di Netlify _headers:

/admin/*
  Access-Control-Allow-Origin: *

Issue: “Images Not Showing”

Solusi:
Check media_folder dan public_folder paths:

media_folder: "static/images/uploads"  # Local path
public_folder: "/images/uploads"         # URL path

Best Practices

1. Content Structure

collections:
  - name: "posts"
    label: "Posts"
    folder: "content/posts"
    path: "{{slug}}/index"  # Page bundle structure
    media_folder: ""
    public_folder: ""
    fields:
      - { label: "Title", name: "title", widget: "string" }
      - { label: "Body", name: "body", widget: "markdown" }

2. Validation

fields:
  - { 
      label: "Title",
      name: "title",
      widget: "string",
      pattern: ['.{10,100}', "Must have 10-100 characters"]
    }
  - { 
      label: "Email",
      name: "email",
      widget: "string",
      pattern: ['^\S+@\S+\.\S+$', "Must be a valid email"]
    }

3. Default Values

fields:
  - { 
      label: "Draft",
      name: "draft",
      widget: "boolean",
      default: true  # Default ke draft
    }
  - { 
      label: "Date",
      name: "date",
      widget: "datetime",
      default: ""
    }

Kesimpulan

Netlify CMS dengan Hugo memberikan:

Git-based: Version control untuk content
Editorial Workflow: Draft, review, publish
User-friendly: Non-technical authors bisa contribute
Real-time Preview: Preview sebelum publish
Media Management: Upload dan organize images
Customizable: Widgets dan fields bisa dikustomisasi

Ideal untuk:

  • Teams dengan content writers non-technical
  • Editorial workflow dengan approval process
  • Client sites yang perlu CMS sederhana

Artikel Terkait

Link Postingan : https://www.tirinfo.com/cara-integrasi-netlify-cms-hugo/

Hendra WIjaya
Tirinfo
6 minutes.
3 February 2026