Panduan membangun blog serverless berdomain kustom dengan tumpukan Hugo + GitHub + Decap CMS + Cloudflare Pages
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)
- Akun GitHub
- Akun Cloudflare (untuk Pages)
- Git (lokal)
- Hugo (untuk pratinjau lokal dan build di Cloudflare)
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 (gantibaseURL
dengan URL produksi)functions/api/*.js
— Cloudflare Pages Functions (/api/auth
dan/api/callback
untuk GitHub OAuth)layouts/_default/*.html
— Template Hugostatic/admin/
— Antarmuka dan konfigurasi Decap CMSstatic/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
-
Buat repositori kosong di GitHub
- Navigasi: GitHub > New repository
- Contoh nama:
my-hugo-blog
-
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
-
-
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
danfunctions/api/callback.js
bisa digunakan apa adanya (menggunakanurl.origin
, jadi tidak ada hardcode bergantung lingkungan).
-
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)
-
Navigasi: GitHub > Settings > Developer settings > OAuth Apps > New OAuth App
-
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/callback
← krusial
- Application name:
-
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
-
Navigasi: Dasbor Cloudflare > Pages > Create a project > Connect to Git
-
Hubungkan ke GitHub dan pilih repositori
my-hugo-blog
-
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)
-
-
Tekan Save and Deploy dan tunggu deploy perdana
- Jika sukses, Cloudflare akan mengeluarkan URL pratinjau
*.pages.dev
- Jika sukses, Cloudflare akan mengeluarkan URL pratinjau
2-4. Menetapkan domain kustom www.example.com
-
Navigasi: Cloudflare > Pages > (proyek terkait) > Custom domains > Set up a custom domain
-
Tambahkan
www.example.com
-
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
- Catat informasi DNS record yang ditampilkan Cloudflare (contoh:
-
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
- Buka
https://www.example.com/admin/
- Klik tombol seperti “Login with GitHub” dan izinkan OAuth GitHub
- 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
- Buka
https://www.example.com/admin/
- Di navigasi kiri pilih Blog → New blog
- Isi
Title
,Publish Date
,Description
,Body
- 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
-
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
-
-
Contoh area yang sering diubah
layouts/_default/baseof.html
—<head>
serta header/footerlayouts/_default/index.html
— daftar “artikel terbaru” di halaman depanlayouts/_default/single.html
— tubuh halaman artikelstatic/css/main.css
— warna, font, jarak
-
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
ataumaster
) selaras antaraconfig.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
dihugo.toml
bila ingin memperluas keluaran seperti RSS atau sitemap
5. Masalah umum dan solusi
- CMS hanya memutar layar login
→ Pastikan Callback URL OAuth GitHub adalahhttps://www.example.com/api/callback
dan environment variable CloudflareGITHUB_CLIENT_ID/SECRET
benar - Halaman utama terbuka tetapi artikel 404
→ Periksa apakahbaseURL
dihugo.toml
sudah sama dengan domain sebenarnya. Cek pula log build Cloudflare untuk memastikanpublic/
terbentuk - /admin kosong putih
→ Pastikan pemuatan Decap CMS distatic/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.
-
Sitemap dan robots.txt
- Daftarkan
public/sitemap.xml
(yang otomatis dibuat Hugo) ke Google Search Console. - Tambahkan aturan crawler di
static/robots.txt
.
- Daftarkan
-
Pengaturan OGP dan Twitter Card
- Tambahkan
<meta property="og:title">
,<meta property="og:image">
, dan sejenisnya dilayouts/_default/baseof.html
. - Membantu tampilan saat dibagikan di media sosial.
- Tambahkan
-
Konversi gambar ke WebP
- Manfaatkan
resources/_gen/images/
dan pipeline Hugo untuk mengonversi ke WebP otomatis. - Mempercepat waktu muat halaman.
- Manfaatkan
-
Menambah kategori dan tag
- Isi
categories
dantags
di Front Mattercontent/blog/
, lalu tampilkan tag cloud lewat template.
- Isi
-
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.