Situs ini dulu saya jalankan di domain kustom yang dihubungkan ke Google Sites generasi lama (masa Google Apps). Layanan warisan itu dihentikan secara global pada 2021, jadi https://www.ixam.net sempat terbengkalai lama. Akhirnya saya putuskan untuk kembali memanfaatkannya. Targetnya adalah arsitektur serverless yang tidak bergantung pada layanan yang bisa ditutup sepihak, dan saya membangun ulang situs ini sambil berduet dengan ChatGPT.

Saya kira perjalanan ini akan mudah kalau ditemani ChatGPT, tetapi kenyataannya berbeda. Saya kerap lupa menyampaikan persyaratan, troubleshooting kami jalan di tempat, dan saya sampai harus menelusuri repositori seperti https://github.com/patrickgrey/website/ agar bisa lanjut. Berat, tetapi pada akhirnya kami menuntaskannya sebagai kolaborasi.

Pengalaman ini terasa seperti membimbing rekan kerja yang sangat mumpuni tetapi sering salah asumsi supaya kembali ke jalur—yang secara tak langsung menunjukkan bahwa AI sudah benar-benar berguna. Namun semakin rumit masalahnya, semakin mudah saya kesal oleh kesalahan yang muncul.

Kesimpulan sementara saya: alih-alih memaksa AI mengingat konteks percakapan yang panjang, lebih baik kita jeda dulu, merangkum keadaan terakhir, lalu membuka utas baru dengan instruksi yang lebih rapi.

Meski begitu, saya sendirian jelas tidak sanggup mengejar porsi riset, uji coba, dan belajar sebanyak itu. Produktivitas yang diberikan AI generatif memang luar biasa.


Tujuan

  • Menayangkan blog di https://www.example.com/
  • Masuk ke Decap CMS melalui https://www.example.com/admin/ dan membuat artikel
  • Artikel baru dikomit ke GitHub dan dideploy otomatis ke Cloudflare
  • Biaya operasional tambahan tetap nol (biaya registrasi domain di luar bahasan)

0. Prasyarat dan struktur folder

0-1. Prasyarat

  • Domain (misal example.com) sudah terdaftar

  • Boleh memakai Windows atau macOS (contoh perintah disertakan untuk keduanya)

  • Layanan yang digunakan (kuota gratis sudah cukup)

0-2. Contoh struktur repositori

hugo.toml
content/
  blog/
  posts/
data/
functions/
  api/
    auth.js
    callback.js
layouts/
  _default/
    baseof.html
    index.html
    list.html
    single.html
static/
  _headers
  admin/
    config.yml
    index.html
  css/
    main.css

Struktur ini memegang peran berikut:

  • hugo.toml — Konfigurasi seluruh situs (ganti baseURL dengan URL produksi)
  • functions/api/*.js — Cloudflare Pages Functions (/api/auth dan /api/callback untuk GitHub OAuth)
  • layouts/_default/*.html — Template Hugo
  • static/admin/ — Antarmuka dan konfigurasi Decap CMS
  • static/css/main.css — Gaya visual (CSS)
  • static/_headers — Pengaturan header HTTP Cloudflare Pages (opsional)

Catatan: dalam panduan ini, bagian yang perlu disesuaikan akan ditandai sebagai “cek penggantian”.


1. Persiapan awal

1-1. Membuat akun GitHub

  • Buka GitHub di browser dan lakukan pendaftaran

1-2. Membuat akun Cloudflare

  • Buka Cloudflare di browser dan daftar

1-3. Memasang Git dan Hugo

  • Windows (PowerShell)

    PS C:\Users\alice> winget install Git.Git
    PS C:\Users\alice> winget install Hugo.Hugo.Extended
    
  • macOS (Terminal)

    mac:~ dev$ brew install git
    mac:~ dev$ brew install hugo
    

2. Langkah utama (hingga deploy)

2-1. Menyiapkan repositori lokal

  1. Buat repositori kosong di GitHub

    • Navigasi: GitHub > New repository
    • Contoh nama: my-hugo-blog
  2. Kloning ke lokal

    • Windows

      PS C:\work> git clone https://github.com/<YOUR_GH_USERNAME>/my-hugo-blog.git
      PS C:\work> cd .\my-hugo-blog
      
    • macOS

      mac:~/work dev$ git clone https://github.com/<YOUR_GH_USERNAME>/my-hugo-blog.git
      mac:~/work dev$ cd my-hugo-blog
      
  3. Letakkan seluruh folder dan file dari struktur di atas tepat di bawah repositori ini

Cek penggantian (wajib disesuaikan)

  • hugo.toml

    baseURL = "https://www.example.com"   # ← ganti dengan URL produksi (contoh: https://www.example.com)
    languageCode = "ja-jp"
    title = "Example Blog"
    publishDir = "public"
    
    [permalinks]
      blog = "/blog/:year/:month/:slug/"
    
  • static/admin/config.yml

    backend:
      name: github
      repo: <YOUR_GH_USERNAME>/my-hugo-blog   # ← ganti dengan repositori GitHub milikmu
      branch: master                          # ← sesuaikan dengan nama branch utama (main atau master)
      base_url: https://www.example.com       # ← ganti dengan URL produksi
      auth_endpoint: /api/auth                # ← endpoint Functions (tetap)
    media_folder: static/uploads
    public_folder: /uploads
    

Referensi: functions/api/auth.js dan functions/api/callback.js bisa digunakan apa adanya (menggunakan url.origin, jadi tidak ada hardcode bergantung lingkungan).

  1. Komit dan push perdana

    • Windows

      PS C:\work\my-hugo-blog> git add -A
      PS C:\work\my-hugo-blog> git commit -m "Initial commit: Hugo + [DecapCMS](https://decapcms.org/) + CF Pages"
      PS C:\work\my-hugo-blog> git push -u origin master
      
    • macOS

      mac:~/work/my-hugo-blog dev$ git add -A
      mac:~/work/my-hugo-blog dev$ git commit -m "Initial commit: Hugo + [DecapCMS](https://decapcms.org/) + CF Pages"
      mac:~/work/my-hugo-blog dev$ git push -u origin master
      

2-2. Membuat GitHub OAuth App (untuk login Decap CMS)

  1. Navigasi: GitHub > Settings > Developer settings > OAuth Apps > New OAuth App

  2. Isi formulir:

    • Application name: [DecapCMS](https://decapcms.org/) for my-hugo-blog
    • Homepage URL: https://www.example.com
    • Authorization callback URL: https://www.example.com/api/callbackkrusial
  3. Setelah dibuat, simpan:

    • Client ID
    • Client Secret (buat baru dan catat)

Nantinya keduanya dimasukkan ke environment variable Cloudflare Pages.


2-3. Membuat proyek Cloudflare Pages

  1. Navigasi: Dasbor Cloudflare > Pages > Create a project > Connect to Git

  2. Hubungkan ke GitHub dan pilih repositori my-hugo-blog

  3. Atur build:

    • Framework preset: None (atau biarkan Cloudflare mendeteksi otomatis)

    • Build command: hugo

    • Build output directory: public

    • Environment variable:

      • HUGO_VERSION = 0.128.0 (contoh; samakan dengan versi Hugo lokal agar aman)
      • GITHUB_CLIENT_ID = (isi dari langkah sebelumnya)
      • GITHUB_CLIENT_SECRET = (isi dari langkah sebelumnya)
  4. Tekan Save and Deploy dan tunggu deploy perdana

    • Jika sukses, Cloudflare akan mengeluarkan URL pratinjau *.pages.dev

2-4. Menetapkan domain kustom www.example.com

  1. Navigasi: Cloudflare > Pages > (proyek terkait) > Custom domains > Set up a custom domain

  2. Tambahkan www.example.com

  3. Bila domain tidak dikelola di Cloudflare:

    • Catat informasi DNS record yang ditampilkan Cloudflare (contoh: CNAME www -> <project>.pages.dev)
    • Buka panel registrar tempat domain dikelola dan buat record tersebut sesuai konsep masing-masing registrar
  4. Setelah propagasi, pastikan https://www.example.com/ bisa dibuka

Tip: jika domain sudah dikelola di Cloudflare, tombol setup akan otomatis membuat DNS yang diperlukan.


2-5. Masuk ke dasbor Decap CMS

  1. Buka https://www.example.com/admin/
  2. Klik tombol seperti “Login with GitHub” dan izinkan OAuth GitHub
  3. Pada percobaan pertama GitHub akan menanyakan “Authorize this app?”—klik Authorize

Jika login gagal, periksa ulang Callback URL OAuth dan environment variable Cloudflare (GITHUB_CLIENT_ID/SECRET).


3. Operasional (membuat artikel, sinkron lokal, edit template)

3-1. Membuat artikel melalui CMS

  1. Buka https://www.example.com/admin/
  2. Di navigasi kiri pilih BlogNew blog
  3. Isi Title, Publish Date, Description, Body
  4. Klik Publish → otomatis melakukan commit ke GitHub → Cloudflare akan men-deploy

Markdown yang dihasilkan tersimpan di content/blog/.

3-2. Menyegarkan repositori lokal (sesudah posting via CMS)

  • Windows

    PS C:\work\my-hugo-blog> git pull origin master
    
  • macOS

    mac:~/work/my-hugo-blog dev$ git pull origin master
    

Dengan begitu kita bisa menarik artikel terbaru ke lokal dan mengedit template atau CSS dengan aman.

3-3. Mengotak-atik desain atau template secara lokal

  1. Jalankan server pengembangan lokal

    • Windows

      PS C:\work\my-hugo-blog> hugo server -D
      
    • macOS

      mac:~/work/my-hugo-blog dev$ hugo server -D
      
    • Buka http://localhost:1313/ di browser untuk memeriksa

  2. Contoh area yang sering diubah

    • layouts/_default/baseof.html<head> serta header/footer
    • layouts/_default/index.html — daftar “artikel terbaru” di halaman depan
    • layouts/_default/single.html — tubuh halaman artikel
    • static/css/main.css — warna, font, jarak
  3. Komit dan push perubahan

    • Windows

      PS C:\work\my-hugo-blog> git add -A
      PS C:\work\my-hugo-blog> git commit -m "Update theme/layouts"
      PS C:\work\my-hugo-blog> git push
      
    • macOS

      mac:~/work/my-hugo-blog dev$ git add -A
      mac:~/work/my-hugo-blog dev$ git commit -m "Update theme/layouts"
      mac:~/work/my-hugo-blog dev$ git push
      
    • Beberapa detik hingga menit kemudian Cloudflare akan men-deploy otomatis

3-4. Catatan singkat soal branch

  • Pastikan nama branch default (main atau master) selaras antara config.yml Decap CMS dan pengaturan Cloudflare
  • Jika ingin pratinjau, buat Pull Request di GitHub: Cloudflare Pages akan otomatis membuat lingkungan Preview

4. Pelengkap opsional

4-1. Contoh static/_headers (non-cache untuk admin)

/admin/*
  Cache-Control: no-store

4-2. robots dan sitemap (opsional)

  • Siapkan static/robots.txt untuk mengendalikan crawler
  • Tambahkan outputs di hugo.toml bila ingin memperluas keluaran seperti RSS atau sitemap

5. Masalah umum dan solusi

  • CMS hanya memutar layar login
    → Pastikan Callback URL OAuth GitHub adalah https://www.example.com/api/callback dan environment variable Cloudflare GITHUB_CLIENT_ID/SECRET benar
  • Halaman utama terbuka tetapi artikel 404
    → Periksa apakah baseURL di hugo.toml sudah sama dengan domain sebenarnya. Cek pula log build Cloudflare untuk memastikan public/ terbentuk
  • /admin kosong putih
    → Pastikan pemuatan Decap CMS di static/admin/index.html tidak diblokir (misalnya oleh ekstensi browser). Matikan sementara dan muat ulang

6. Ringkasan titik penggantian / pengaturan (rekap)

File / pengaturan Kunci Nilai contoh yang harus disesuaikan
hugo.toml baseURL https://www.example.com
static/admin/config.yml repo <YOUR_GH_USERNAME>/my-hugo-blog
static/admin/config.yml branch Samakan dengan branch default (main atau master)
static/admin/config.yml base_url https://www.example.com
Cloudflare Pages HUGO_VERSION Contoh: 0.128.0
Cloudflare Pages GITHUB_CLIENT_ID Client ID dari GitHub OAuth App
Cloudflare Pages GITHUB_CLIENT_SECRET Client Secret dari GitHub OAuth App
GitHub OAuth App Callback URL https://www.example.com/api/callback
Cloudflare Pages Custom domain Tambahkan www.example.com

7. Lampiran: contoh file (ganti bagian yang perlu)

7-1. functions/api/auth.js

// Cloudflare Pages Functions (/api/auth)
export async function onRequest(context) {
  const { request, env } = context;
  const client_id = env.GITHUB_CLIENT_ID;
  try {
    const url = new URL(request.url);
    const redirectUrl = new URL('https://github.com/login/oauth/authorize');
    redirectUrl.searchParams.set('client_id', client_id);
    redirectUrl.searchParams.set('redirect_uri', `${url.origin}/api/callback`);
    redirectUrl.searchParams.set('scope', 'repo user');
    redirectUrl.searchParams.set('state', crypto.getRandomValues(new Uint8Array(12)).join(''));
    return Response.redirect(redirectUrl.href, 302);
  } catch (err) {
    return new Response(String(err?.message || err), { status: 500 });
  }
}

7-2. functions/api/callback.js

function renderBody(status, content) {
  const html = `
  <script>
    const receiveMessage = (message) => {
      window.opener.postMessage(
        'authorization:github:${status}:${JSON.stringify(content)}',
        message.origin
      );
      window.removeEventListener("message", receiveMessage, false);
    }
    window.addEventListener("message", receiveMessage, false);
    window.opener.postMessage("authorizing:github", "*");
  </script>`;
  return new Blob([html]);
}

export async function onRequest(context) {
  const { request, env } = context;
  const client_id = env.GITHUB_CLIENT_ID;
  const client_secret = env.GITHUB_CLIENT_SECRET;
  try {
    const url = new URL(request.url);
    const code = url.searchParams.get('code');
    const response = await fetch('https://github.com/login/oauth/access_token', {
      method: 'POST',
      headers: { 'content-type': 'application/json', 'user-agent': 'cf-pages-oauth', 'accept': 'application/json' },
      body: JSON.stringify({ client_id, client_secret, code }),
    });
    const result = await response.json();
    if (result.error) {
      return new Response(renderBody('error', result), { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 401 });
    }
    const token = result.access_token;
    const provider = 'github';
    return new Response(renderBody('success', { token, provider }), { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 200 });
  } catch (error) {
    return new Response(error.message, { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 500 });
  }
}

7-3. static/admin/config.yml

backend:
  name: github
  repo: <YOUR_GH_USERNAME>/my-hugo-blog
  branch: main
  base_url: https://www.example.com
  auth_endpoint: /api/auth

media_folder: static/uploads
public_folder: /uploads

collections:
  - name: 'blog'
    label: 'Blog'
    folder: 'content/blog'
    create: true
    slug: '{{slug}}'
    editor:
      preview: false
    fields:
      - { label: 'Title', name: 'title', widget: 'string' }
      - { label: 'Publish Date', name: 'date', widget: 'datetime' }
      - { label: 'Description', name: 'description', widget: 'string' }
      - { label: 'Body', name: 'body', widget: 'markdown' }


9. Poin optimasi SEO & performa

Setelah blog berdiri, lakukan langkah berikut untuk meningkatkan posisi pencarian dan kecepatan tampil.

  1. Sitemap dan robots.txt

    • Daftarkan public/sitemap.xml (yang otomatis dibuat Hugo) ke Google Search Console.
    • Tambahkan aturan crawler di static/robots.txt.
  2. Pengaturan OGP dan Twitter Card

    • Tambahkan <meta property="og:title">, <meta property="og:image">, dan sejenisnya di layouts/_default/baseof.html.
    • Membantu tampilan saat dibagikan di media sosial.
  3. Konversi gambar ke WebP

    • Manfaatkan resources/_gen/images/ dan pipeline Hugo untuk mengonversi ke WebP otomatis.
    • Mempercepat waktu muat halaman.
  4. Menambah kategori dan tag

    • Isi categories dan tags di Front Matter content/blog/, lalu tampilkan tag cloud lewat template.
  5. Memasukkan data terstruktur

    • Tambahkan JSON-LD berisi informasi artikel agar mesin pencari menampilkan rich result.

8. Ringkasan

  • Hubungkan repositori GitHub ke Cloudflare Pages, atur OAuth GitHub untuk Decap CMS, dan sematkan domain kustom untuk menyelesaikan rangkaian
  • Artikel dibuat dari /admin/ → otomatis commit ke GitHub → Cloudflare men-deploy
  • Desain dan template bisa diedit lokal sambil menjalankan hugo server, lalu dipush melalui Git

Dengan ini, ekosistem blog serverless berbasis Hugo + GitHub + Decap CMS + Cloudflare siap digunakan dan dioperasikan.

Satu catatan lagi: situs versi awal memang sudah jadi dengan biaya operasional yang sangat ringan, tetapi fitur seperti pencarian situs, kategori, dan tag/tag cloud masih terasa minim dibandingkan platform blog mapan. Untuk sementara saya terima dulu kekurangan itu—selanjutnya ingin saya kembangkan pelan-pelan bersama ChatGPT.