Lewati ke konten
Kembali ke Blog

Cara Integrasi Netlify CMS dengan Hugo untuk Headless CMS

· · 9 menit baca

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 Settings → Identity
  2. Enable Identity → Click “Enable Identity”
  3. Services → Git 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: &quot;Rating&quot;, name: &quot;rating&quot;, widget: &quot;number&quot;, value_type: &quot;int&quot;, min: 1, max: 5 }
  - { label: &quot;Layout&quot;, name: &quot;layout&quot;, widget: &quot;select&quot;, options: [&quot;default&quot;, &quot;full-width&quot;, &quot;sidebar&quot;] }
  - { label: &quot;Color&quot;, name: &quot;color&quot;, widget: &quot;color&quot; }
  - { label: &quot;Content&quot;, name: &quot;content&quot;, widget: &quot;text&quot; }
  - { label: &quot;Code&quot;, name: &quot;code&quot;, widget: &quot;code&quot; }

  # Lists
  - { label: &quot;Categories&quot;, name: &quot;categories&quot;, widget: &quot;list&quot; }
  - { label: &quot;Tags&quot;, name: &quot;tags&quot;, widget: &quot;list&quot;, allow_add: true }

  # Object
  - label: &quot;Author&quot;
    name: &quot;author&quot;
    widget: &quot;object&quot;
    fields:
      - { label: &quot;Name&quot;, name: &quot;name&quot;, widget: &quot;string&quot; }
      - { label: &quot;Email&quot;, name: &quot;email&quot;, widget: &quot;string&quot; }
      - { label: &quot;Bio&quot;, name: &quot;bio&quot;, widget: &quot;text&quot; }

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: &quot;Social Links&quot;
    name: &quot;social&quot;
    file: &quot;config/_default/social.yaml&quot;
    fields:
      - label: &quot;Social Links&quot;
        name: &quot;links&quot;
        widget: &quot;list&quot;
        fields:
          - { label: &quot;Platform&quot;, name: &quot;platform&quot;, widget: &quot;string&quot; }
          - { label: &quot;URL&quot;, name: &quot;url&quot;, widget: &quot;string&quot; }

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

Preview Links

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

Ditulis oleh

Hendra Wijaya

Hanya hamba Allah Ta'ala yang berusaha berbuat baik..

Tinggalkan Komentar

Email tidak akan ditampilkan.

Facebook X / Twitter WhatsApp