livereload.js Terikut ke Production Saat `hugo deploy`: Penyebab dan Solusi
9 min read

livereload.js Terikut ke Production Saat `hugo deploy`: Penyebab dan Solusi

Di Hugo, livereload.js seharusnya hanya muncul saat mode development — file ini bertanggung jawab membuat browser otomatis refresh setiap kali ada perubahan kode, fitur yang sangat membantu saat menulis konten atau mengubah template. Tapi tidak jarang file ini justru ikut ter-deploy ke production meskipun deployment sudah memakai hugo deploy, command resmi Hugo untuk publish. Masalah ini sering disangka bug Hugo, padahal akar masalahnya adalah kesalahan pemahaman terhadap alur build dan deploy yang sebenarnya terpisah secara tegas. Artikel ini membedah mekanisme di balik penyuntikan livereload, kenapa hugo deploy tidak pernah melakukan build ulang, dan bagaimana menyusun workflow yang membuat masalah ini tidak mungkin terjadi lagi.

Pemisahan Konsep: Build vs Deploy di Hugo

Hugo memisahkan dua proses ini secara tegas, dan memahami pemisahan ini adalah kunci untuk seluruh pembahasan di artikel ini:

ProsesFungsi
BuildMenghasilkan static files ke folder public/
DeployMeng-upload isi folder public/ apa adanya ke remote
flowchart LR
    A[Source: content, layouts, config] -->|hugo / hugo server| B[Folder public/]
    B -->|hugo deploy| C[Remote: S3, GCS, dll]
    style B fill:#f9f9f9

Command yang berkaitan dengan kedua proses ini:

CommandPeran
hugoBuild untuk production
hugo serverBuild untuk development + LiveReload aktif
hugo deployHanya meng-upload isi public/, tidak melakukan build

Satu fakta yang krusial dan jadi akar dari seluruh masalah di artikel ini: hugo deploy tidak pernah melakukan build ulang. Ia murni proses upload — apapun yang ada di folder public/ saat command ini dijalankan, itulah yang akan terkirim ke remote, baik isinya valid untuk production maupun tidak.


Bagaimana Hugo Menyuntikkan livereload.js saat hugo server

Sebelum membahas penyebab masalahnya, penting memahami mekanisme di balik kenapa livereload.js bisa muncul di output HTML sama sekali. Ini bukan file statis yang sengaja kamu taruh di folder static/ — Hugo menyuntikkannya secara otomatis ke setiap halaman HTML saat server development berjalan.

sequenceDiagram
    participant Dev as Developer
    participant Hugo as hugo server
    participant Browser
    Dev->>Hugo: Jalankan hugo server
    Hugo->>Hugo: Set .Site.IsServer = true
    Hugo->>Hugo: Render HTML + suntik <script src="/livereload.js">
    Hugo->>Browser: Serve halaman dengan livereload aktif
    Dev->>Hugo: Ubah file content/layout
    Hugo->>Browser: Kirim sinyal lewat WebSocket
    Browser->>Browser: Auto-refresh halaman

Mekanismenya bergantung pada satu variabel internal: .Site.IsServer. Variabel ini bernilai true hanya ketika Hugo dijalankan lewat hugo server, dan false untuk build biasa lewat hugo. Saat .Site.IsServer bernilai true, Hugo menyuntikkan elemen <script> yang menunjuk ke livereload.js — script kecil yang membuka koneksi WebSocket ke server development, mendengarkan sinyal perubahan file, lalu memicu refresh otomatis di browser.

Karena penyuntikan ini bersifat otomatis dan terjadi di level engine Hugo (bukan sesuatu yang kamu tulis manual di template, kecuali kamu memang menambahkannya sendiri), banyak developer tidak sadar bahwa script ini benar-benar tertanam langsung di HTML hasil build — bukan dimuat terpisah lewat request tambahan yang mudah diabaikan.

livereload.js sendiri sebenarnya disajikan langsung oleh proses hugo server yang sedang berjalan, bukan file fisik yang tersimpan di folder public/. Yang ikut ter-deploy ke production bukan file livereload.js-nya, melainkan referensi <script> tag ke file itu di HTML hasil build — yang di production akan gagal dimuat (404) sekaligus menjadi indikasi bahwa output yang ter-upload bukan build production yang benar.

Penyebab livereload.js Terikut ke Production

Kasus yang paling sering terjadi mengikuti pola workflow seperti ini:

hugo server
# (stop server dengan Ctrl+C)
hugo deploy
sequenceDiagram
    participant Dev as Developer
    participant FS as Folder public/
    participant Remote
    Dev->>FS: hugo server (isi public/ dengan livereload.js disuntik)
    Dev->>Dev: Stop server, public/ TIDAK dibersihkan
    Dev->>Remote: hugo deploy
    Note over Remote: Upload isi public/ apa adanya<br/>termasuk script livereload.js

Penjelasannya bertahap:

  1. hugo server membangun HTML dalam mode development, dan otomatis menyuntikkan livereload.js karena .Site.IsServer bernilai true
  2. Output ini disimpan ke folder public/ di disk — sama seperti hasil hugo build biasa
  3. hugo deploy dijalankan setelahnya, dan karena ia tidak pernah melakukan build ulang (seperti dijelaskan di bagian sebelumnya), ia menganggap isi public/ sudah valid dan langsung meng-upload-nya
  4. Akibatnya, artifact hasil development — lengkap dengan referensi livereload.js — ikut terkirim ke production

Akar masalahnya bukan hugo deploy yang salah berperilaku, melainkan asumsi keliru bahwa hugo deploy akan “tahu” untuk membangun ulang versi production terlebih dahulu. Padahal sesuai pemisahan konsep di bagian sebelumnya, deploy memang murni tugas upload.


Arti Pesan “No changes required.”

Saat menjalankan hugo deploy, terkadang muncul pesan:

No changes required.

Pesan ini sering disalahartikan sebagai error atau tanda ada yang salah, padahal sebenarnya berarti sebaliknya — deploy berjalan normal, hanya saja tidak ada perubahan yang perlu di-upload. Di balik layar, hugo deploy membandingkan isi folder public/ lokal dengan apa yang sudah ada di remote, biasanya berbasis checksum atau hash tiap file, lalu hanya meng-upload file yang berbeda.

flowchart TD
    A[hugo deploy dijalankan] --> B[Hitung hash setiap file di public/ lokal]
    B --> C[Bandingkan dengan hash file di remote]
    C --> D{Ada perbedaan?}
    D -- Tidak --> E["No changes required."]
    D -- Ya --> F[Upload hanya file yang berbeda]

Pesan ini bukan error — tapi justru di sinilah jebakannya muncul. Kalau folder public/ yang dibandingkan itu berisi hasil build dari hugo server (lengkap dengan livereload.js), dan kebetulan isi itu sudah pernah ter-upload sebelumnya, Hugo tetap menganggapnya sebagai state yang valid dan konsisten — padahal sebenarnya artifact yang salah sudah dianggap final sejak upload pertama.


Peran Environment: development vs production

Hugo menentukan banyak perilaku build — termasuk apakah livereload disuntikkan atau tidak — berdasarkan environment yang aktif. Penting dipahami bahwa environment ini sebenarnya cuma sebuah string biasa yang dibaca lewat hugo.Environment di template, bukan mekanisme switch ajaib yang otomatis mengubah perilaku tanpa kondisi eksplisit di kode.

Cek environment yang aktif saat ini:

hugo env

Output yang diharapkan untuk build production:

Environment: production

Default environment Hugo sebenarnya bergantung pada command yang dipakai:

CommandEnvironment Default
hugoproduction
hugo serverdevelopment
Override manualHUGO_ENV=production hugo ...

Kalau environment yang aktif masih development saat kamu bermaksud build untuk production — entah karena lupa, atau karena script build custom yang tidak eksplisit men-set environment — konsekuensinya:

  • LiveReload tetap berpotensi aktif tergantung kondisi template
  • .Site.IsServer bisa saja masih true kalau prosesnya berasal dari hugo server
  • Risiko livereload.js ikut ke output produksi jadi sangat tinggi
.Site.IsServer dan hugo.Environment adalah dua hal yang berbeda meski sering tampak berkaitan. .Site.IsServer murni soal apakah proses berasal dari hugo server atau bukan, sementara hugo.Environment adalah label string yang bisa di-override manual lewat HUGO_ENV. Keduanya sebaiknya dicek bersamaan di template untuk kondisi yang benar-benar aman, bukan hanya mengandalkan salah satu saja.

Kesalahan Umum yang Sering Terjadi

Mengira hugo deploy Otomatis Rebuild

# ANTI-PATTERN: asumsi hugo deploy akan build ulang dulu
hugo deploy
# Tidak ada rebuild yang terjadi -- public/ diupload apa adanya

# BENAR: build eksplisit sebelum deploy
hugo --minify
hugo deploy

Deploy Setelah hugo server

# ANTI-PATTERN: public/ masih berisi artifact development
hugo server
# stop server
hugo deploy

# BENAR: bersihkan dan build ulang sebelum deploy
rm -rf public
hugo --minify
hugo deploy

Menggunakan –minify di hugo deploy

# ANTI-PATTERN: --minify bukan flag untuk hugo deploy
hugo deploy --minify
# Error: unknown flag: --minify

# BENAR: --minify dipakai saat build, bukan saat deploy
hugo --minify
hugo deploy

Ketiga kesalahan ini sebenarnya berakar dari satu kesalahpahaman yang sama: menganggap hugo deploy punya kemampuan atau tanggung jawab yang sebenarnya dimiliki oleh command hugo (build), padahal keduanya memang sengaja dipisah Hugo sebagai dua command yang independen.


Workflow Build dan Deploy yang Benar

Langkah aman untuk production menggabungkan tiga hal sekaligus: membersihkan artifact lama, build ulang dengan environment yang benar, baru deploy.

rm -rf public && HUGO_ENV=production hugo --minify && hugo deploy
flowchart TD
    A[rm -rf public] --> B[HUGO_ENV=production hugo --minify]
    B --> C{Build berhasil?}
    C -- Tidak --> D[Perbaiki error, ulangi dari awal]
    C -- Ya --> E[hugo deploy]
    E --> F[Verifikasi: grep livereload di public/]

Penjelasan tiap langkah:

  • rm -rf public menghapus seluruh artifact lama, termasuk kemungkinan sisa hasil hugo server sebelumnya — langkah ini menghilangkan akar masalah secara langsung
  • HUGO_ENV=production hugo --minify membangun ulang dari awal dengan environment yang eksplisit diset ke production, memastikan .Site.IsServer bernilai false
  • hugo deploy meng-upload output yang sudah bersih dan benar-benar mencerminkan build production
Jadikan rangkaian perintah ini sebagai satu script (misalnya deploy.sh atau target di Makefile), bukan diketik manual setiap kali. Menghilangkan langkah manual sama dengan menghilangkan kemungkinan lupa salah satu tahapnya.

Verifikasi Sebelum Deploy

Sebelum menjalankan hugo deploy, lakukan pengecekan eksplisit untuk memastikan tidak ada referensi livereload.js yang tertinggal di output:

grep -R "livereload" public/

Kalau tidak ada hasil yang muncul, output aman untuk production. Kalau ada hasil yang muncul, itu tanda jelas bahwa folder public/ masih berisi artifact dari hugo server dan perlu di-build ulang sebelum deploy dilanjutkan.

Untuk tim yang menjalankan deploy lebih dari sekali atau melibatkan lebih dari satu orang, pertimbangkan menambahkan pengecekan ini sebagai langkah eksplisit di script deploy itu sendiri — bukan sekadar diingat manual setiap kali:

#!/bin/bash
set -e

rm -rf public
HUGO_ENV=production hugo --minify

if grep -R "livereload" public/ > /dev/null; then
  echo "ERROR: livereload.js terdeteksi di build production, deploy dibatalkan."
  exit 1
fi

hugo deploy

Dengan set -e dan pengecekan eksplisit sebelum baris hugo deploy, script ini berhenti otomatis kalau ditemukan indikasi build yang salah — mencegah deploy yang salah terjadi tanpa disadari, alih-alih hanya mengandalkan kewaspadaan manual.


Best Practice Inject LiveReload di Template

Kalau kamu pernah menambahkan livereload secara manual di template (bukan mengandalkan penyuntikan otomatis Hugo), hindari hardcode tanpa kondisi sama sekali:

<!-- ANTI-PATTERN: livereload selalu disertakan tanpa kondisi -->
<script src="/livereload.js"></script>

Pendekatan yang benar memakai conditional berbasis .Site.IsServer:

{{ if .Site.IsServer }}
  {{ partial "livereload.html" . }}
{{ end }}

Untuk lapisan keamanan tambahan, kombinasikan dengan pengecekan environment secara eksplisit — mengingat seperti dibahas sebelumnya, .Site.IsServer dan hugo.Environment adalah dua hal yang berbeda dan idealnya dicek bersamaan:

{{ if and .Site.IsServer (eq hugo.Environment "development") }}
  {{ partial "livereload.html" . }}
{{ end }}

Dengan kombinasi dua kondisi ini, livereload hanya akan muncul kalau keduanya benar — proses berasal dari hugo server dan environment yang aktif memang development. Ini memberi lapisan proteksi tambahan dibanding hanya mengandalkan satu kondisi saja, terutama untuk setup yang kompleks di mana environment bisa di-override secara manual.

Kombinasi dua kondisi (.Site.IsServer dan hugo.Environment) lebih aman daripada satu kondisi saja, tapi tetap tidak menggantikan kebiasaan membersihkan folder public/ sebelum build production. Pertahanan berlapis selalu lebih baik daripada mengandalkan satu mekanisme tunggal.

Tabel Ringkasan Command dan Perilakunya

CommandBuild Ulang?.Site.IsServerEnvironment DefaultRisiko livereload.js
hugo serverYa, terus-menerustruedevelopmentSelalu disuntikkan (memang seharusnya)
hugoYa, sekalifalseproductionTidak ada, kecuali template hardcode
hugo --minifyYa, sekalifalseproductionTidak ada
hugo deployTidakTidak relevanTidak relevanBergantung sepenuhnya pada isi public/ sebelumnya

Ringkasan

  • Hugo memisahkan build (menghasilkan public/) dan deploy (mengupload public/) secara tegas — hugo deploy tidak pernah melakukan build ulang.
  • livereload.js disuntikkan otomatis ke HTML saat .Site.IsServer bernilai true, yang hanya terjadi ketika proses berasal dari hugo server.
  • Penyebab paling umum adalah menjalankan hugo deploy setelah hugo server tanpa build ulang di antaranya — artifact development ikut terupload apa adanya.
  • Pesan “No changes required.” bukan error, tapi bisa menyesatkan kalau artifact yang dibandingkan memang sudah salah sejak upload pertama.
  • hugo.Environment adalah string yang dibaca dari HUGO_ENV atau default command, berbeda dari .Site.IsServer — keduanya idealnya dicek bersamaan di template untuk kondisi paling aman.
  • Workflow aman: rm -rf public untuk membersihkan artifact lama, build ulang eksplisit dengan environment production, baru jalankan hugo deploy.
  • grep -R "livereload" public/ adalah verifikasi cepat sebelum deploy — pertimbangkan menjadikannya langkah otomatis di script deploy, bukan sekadar diingat manual.
  • Di template, gunakan {{ if and .Site.IsServer (eq hugo.Environment "development") }} untuk proteksi berlapis terhadap kondisi environment yang ambigu.

Portofolio