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

Tinggalkan Komentar

Email tidak akan ditampilkan.