Rewrite #
URL rewriting (penulisan ulang URL) adalah salah satu teknik manipulasi lalu lintas HTTP paling mendasar dan penting dalam pengelolaan server web modern. Melalui rewrite, kita dapat mengubah jalur (path) atau query parameter dari permintaan yang masuk secara internal di sisi server sebelum permintaan tersebut diproses lebih lanjut oleh modul penanganan berkas statis atau diteruskan ke server backend. Berbeda dengan pengalihan (redirect) yang melibatkan interaksi langsung dengan browser klien, proses rewrite terjadi sepenuhnya di dalam memori Caddy. Hal ini membuat klien tidak pernah menyadari bahwa URL asli yang mereka minta telah diubah di balik layar. Kita akan membahas secara mendalam perbedaan arsitektural antara rewrite internal dan redirect eksternal, mengeksplorasi penggunaan direktif rewrite dan uri, mempraktikkan operasi manipulasi jalur seperti penghapusan prefiks (strip prefix), menerapkan pola routing untuk Single Page Application (SPA), serta menyusun logika pencocokan ekspresi reguler (regex) untuk skenario perutean backend yang kompleks.
Perbedaan Mendasar Internal Rewrite vs External Redirect #
Untuk merancang infrastruktur web yang efisien dan ramah terhadap mesin pencari (SEO), kita harus memahami dengan jelas kapan harus menggunakan internal rewrite dan kapan harus menggunakan external redirect. Kesalahan dalam memilih kedua metode ini dapat mengakibatkan pemborosan bandwidth jaringan, latensi yang tidak perlu bagi pengguna, atau bahkan rusaknya indeksasi halaman di mesin pencari.
Mekanisme Alur Kerja HTTP #
Perbedaan utama antara keduanya terletak pada di mana keputusan perutean diselesaikan dan bagaimana browser klien merespons instruksi tersebut.
1. External Redirect (Pengalihan Eksternal) #
Ketika kita menerapkan redirect, Caddy akan langsung menghentikan proses evaluasi permintaan saat itu juga dan mengirimkan respons HTTP dengan status kode 3xx (seperti 301 Moved Permanently atau 302 Found) kembali ke browser klien. Respons ini selalu disertai dengan header Location yang menunjukkan URL baru. Browser klien yang menerima respons tersebut akan secara otomatis membuat koneksi TCP baru dan mengirimkan permintaan HTTP GET baru ke URL tujuan. URL pada bilah alamat (address bar) browser akan berubah menunjukkan alamat baru tersebut.
2. Internal Rewrite (Penulisan Ulang Internal) #
Sebaliknya, saat kita menerapkan rewrite, Caddy secara instan memodifikasi URI permintaan di dalam memori internalnya dan melanjutkan alur pemrosesan permintaan (middleware chain) menggunakan URI baru tersebut. Jika setelah rewrite rute mengarah ke berkas statis atau reverse proxy, Caddy akan mengambil konten tersebut dan menyajikannya langsung kepada klien. Browser klien tetap mempertahankan URL asli di bilah alamatnya dan tidak pernah mengetahui bahwa Caddy telah mengubah rute secara internal. Tidak ada putaran jaringan (network round-trip) tambahan yang terjadi.
Mari kita perhatikan alur interaksi kedua mekanisme ini pada diagram sequence berikut:
sequenceDiagram
participant Browser as "Browser Klien"
participant Caddy as "Caddy Web Server"
participant Backend as "Aplikasi Backend"
rect rgb(240, 248, 255)
note over Browser, Caddy: Skenario A: External Redirect (301/302)
Browser->>Caddy: 1. GET /halaman-lama
Caddy-->>Browser: 2. HTTP 301 Moved Permanently (Location: /halaman-baru)
note over Browser: Browser mengubah URL di address bar menjadi /halaman-baru
Browser->>Caddy: 3. GET /halaman-baru (Koneksi Baru)
Caddy->>Backend: 4. Meneruskan Request ke /halaman-baru
Backend-->>Caddy: 5. Mengembalikan Konten Halaman Baru
Caddy-->>Browser: 6. HTTP 200 OK
end
rect rgb(245, 245, 245)
note over Browser, Caddy: Skenario B: Internal Rewrite (Internal)
Browser->>Caddy: 1. GET /halaman-lama
note over Caddy: Caddy mengubah rute secara internal ke /halaman-baru
Caddy->>Backend: 2. Meneruskan Request asli sebagai /halaman-baru
Backend-->>Caddy: 3. Mengembalikan Konten Halaman Baru
Caddy-->>Browser: 4. HTTP 200 OK (URL di browser tetap /halaman-lama)
endTabel Perbandingan Teknis #
Untuk mempermudah kita dalam mengambil keputusan taktis, berikut adalah tabel perbandingan karakteristik antara internal rewrite dan external redirect:
| Karakteristik Analisis | Internal Rewrite | External Redirect |
|---|---|---|
| Lokasi Eksekusi | Sepenuhnya di dalam memori server (Caddy) | Sisi klien (Browser merespons header Location) |
| Putaran Jaringan (Round-Trip) | 1 kali request-response | Minimal 2 kali request-response |
| Bilah Alamat Browser | Tetap menampilkan URL lama | Berubah menampilkan URL baru |
| Status Kode HTTP | Umumnya 200 OK (atau status dari backend) | Status 301, 302, 307, atau 308 |
| Dampak SEO | Tidak memindahkan otoritas peringkat halaman | Memindahkan PageRank/otoritas (terutama 301) |
| Skenario Penggunaan Utama | Single Page Application, penyembunyian API backend | Migrasi nama domain, pemindahan halaman permanen |
Direktif rewrite vs Direktif uri
#
Caddy menyediakan dua direktif utama untuk memanipulasi URI permintaan: rewrite dan uri. Meskipun keduanya tampak serupa karena sama-sama mengubah jalur akses, keduanya dirancang untuk kebutuhan yang berbeda dengan tingkat fleksibilitas yang bervariasi.
1. Direktif rewrite
#
Direktif rewrite adalah instruksi tingkat tinggi yang digunakan untuk mengganti seluruh URI (jalur beserta query string) secara langsung. Ini adalah jalan pintas (shortcut) yang sangat efisien untuk mengalihkan rute tanpa memerlukan manipulasi teks yang kompleks.
Sintaks dasar direktif rewrite adalah sebagai berikut:
rewrite [<matcher>] <to>
Variabel <to> dapat berisi jalur statis baru, placeholder dinamis bawaan Caddy, atau gabungan keduanya. Jika kita melakukan rewrite ke jalur yang menyertakan query parameter baru, Caddy secara otomatis akan menggabungkan query parameter lama dari klien agar tidak hilang, kecuali jika kita menentukan secara eksplisit untuk membuangnya.
2. Direktif uri
#
Direktif uri memberikan kontrol yang jauh lebih granular dan spesifik terhadap bagian-bagian tertentu dari URI permintaan. Dibandingkan langsung mengganti seluruh string URI seperti rewrite, direktif uri memungkinkan kita melakukan operasi transformasi teks khusus pada path dengan beberapa sub-instruksi:
strip_prefix: Menghapus prefiks (awalan) tertentu dari awal jalur permintaan jika cocok.strip_suffix: Menghapus sufiks (akhiran) tertentu dari akhir jalur permintaan jika cocok.replace: Mengganti substring tertentu di dalam jalur dengan string baru.path_regexp: Menggunakan ekspresi reguler (regular expression) untuk mencocokkan dan memanipulasi pola jalur yang kompleks.
Sintaks dasar direktif uri adalah:
uri [<matcher>] <sub-directive> <arguments...>
Membandingkan Kode: Kapan Memilih yang Mana? #
Mari kita lihat perbedaan implementasi nyata antara rewrite dan uri melalui contoh Caddyfile berikut:
# Contoh perbandingan penggunaan direktif
example.com {
# ANTI-PATTERN: Menggunakan rewrite regex untuk menghapus prefiks
# Cara ini tidak efisien karena memaksa parser regex berjalan untuk setiap request
rewrite * /api/v1{path}
# BENAR: Menggunakan direktif uri strip_prefix yang sangat cepat
# Caddy memproses ini pada tingkat memori biner tanpa overhead regex
uri strip_prefix /api/v1
}
Operasi Manipulasi Jalur (Path Manipulation) #
Di lingkungan produksi, kita sering kali perlu melakukan penyesuaian jalur URL untuk menyamarkan struktur direktori internal kita atau untuk menyesuaikan format input yang diharapkan oleh server backend. Berikut adalah teknik manipulasi jalur yang paling sering digunakan menggunakan direktif uri.
1. Menghapus Prefiks Rute (strip_prefix)
#
Ketika kita menjalankan microservices, kita sering mengelompokkan lalu lintas berdasarkan jalur seperti /api/ atau /assets/. Namun, aplikasi backend kita mungkin tidak mengharapkan prefiks tersebut dalam routing internal mereka. Di sinilah kita menggunakan strip_prefix.
# Contoh: Menghapus prefiks /service-a sebelum dikirim ke backend
example.com {
handle /service-a/* {
# Jika klien meminta: /service-a/users/list
# Caddy akan menghapus "/service-a"
uri strip_prefix /service-a
# Backend menerima request sebagai: /users/list
reverse_proxy localhost:8081
}
}
2. Menghapus Akhiran Berkas (strip_suffix)
#
Taktik ini berguna untuk mewujudkan clean URLs dengan menyembunyikan ekstensi berkas asli seperti .html atau .php dari browser klien.
# Contoh: Menyembunyikan ekstensi .html
example.com {
root * /var/www/html
# Jika klien meminta: /about
# Caddy akan mencoba menyajikan berkas /about.html secara internal
@html_files file {path}.html
rewrite @html_files {path}.html
# Jika klien mengakses /about.html secara langsung, kita strip akhiran .html
# agar URL di browser tetap bersih tanpa ekstensi
@has_html_suffix path *.html
handle @has_html_suffix {
uri strip_suffix .html
# Caddy akan mengirim redirect 301 untuk memaksa URL bersih
redir {path}
}
file_server
}
3. Mengganti Substring Tertentu (replace)
#
Kita dapat menggunakan sub-direktif replace untuk melakukan pencarian dan penggantian string sederhana pada jalur tanpa menggunakan ekspresi reguler.
# Contoh: Mengubah versi API secara dinamis di URL
example.com {
# Jika klien meminta: /api/v2/products
# Jalur akan diubah menjadi: /api/v3/products
uri replace /api/v2/ /api/v3/
reverse_proxy localhost:8080
}
Pola SPA (Single Page Application) Routing #
Aplikasi frontend modern yang dibangun menggunakan framework seperti React, Svelte, Vue, atau Angular mengandalkan client-side routing. Artinya, navigasi antar halaman ditangani sepenuhnya oleh JavaScript di browser klien tanpa melakukan request halaman baru ke server.
Namun, tantangan muncul ketika pengguna menyegarkan (refresh) browser pada rute kustom seperti https://app.example.com/dashboard/settings. Server Caddy secara bawaan akan mencari berkas fisik bernama /dashboard/settings atau /dashboard/settings/index.html di dalam direktori root. Karena berkas tersebut tidak ada (semua logika halaman dibungkus di dalam satu berkas index.html utama), Caddy akan mengembalikan respons 404 Not Found.
Untuk mengatasi masalah ini, kita harus menerapkan pola fallback di mana semua permintaan rute non-berkas dialihkan secara internal ke index.html.
Solusi Terbaik Menggunakan try_files
#
Direktif try_files adalah cara paling elegan dan efisien untuk menangani skenario SPA. Caddy akan memeriksa daftar jalur yang diberikan secara berurutan. Jika jalur pertama cocok dengan berkas fisik yang ada di disk, Caddy akan langsung menyajikannya. Jika tidak ada berkas yang cocok, Caddy akan beralih ke jalur berikutnya hingga opsi terakhir yaitu melakukan rewrite ke index.html.
# Pola SPA Produksi yang Sangat Direkomendasikan
app.example.com {
root * /var/www/my-spa-app/dist
# Jalur Pemeriksaan:
# 1. Cek apakah ada berkas fisik sesuai {path} (e.g. /css/style.css)
# 2. Cek apakah ada direktori fisik sesuai {path}/
# 3. Jika keduanya tidak ada, lakukan rewrite internal ke /index.html
try_files {path} {path}/ /index.html
# Aktifkan layanan berkas statis
file_server
}
Mengapa Pendekatan try_files Lebih Baik? #
Mari kita bandingkan dengan pendekatan naif yang sering dilakukan pemula menggunakan explicit matcher yang rentan kesalahan:
# Contoh Perbandingan Pendekatan SPA
app.example.com {
root * /var/www/my-spa-app/dist
# ANTI-PATTERN: Menulis matcher manual untuk semua rute aset statis
# JANGAN menggunakan cara ini karena setiap kali ada aset baru (e.g. .webp),
# kita harus mengedit berkas Caddyfile produksi kita
@spa_rules {
not path /index.html
not path *.js
not path *.css
not path *.png
}
rewrite @spa_rules /index.html
# BENAR: Menggunakan try_files yang secara otomatis mengecek keberadaan berkas fisik
# tanpa memedulikan apa pun ekstensi berkasnya di masa depan
try_files {path} /index.html
file_server
}
Strip Prefix Bersama reverse_proxy
#
Salah satu arsitektur deployment paling populer adalah menggunakan Caddy sebagai gerbang API (API Gateway) tunggal yang mendistribusikan lalu lintas ke berbagai microservices di backend berdasarkan jalur URL.
Misalnya:
- Permintaan ke
example.com/api/v1/users/*diteruskan keuser-service:8081. - Permintaan ke
example.com/api/v1/orders/*diteruskan keorder-service:8082.
Namun, sering kali server backend microservices kita dirancang secara independen dan tidak mengetahui bahwa mereka ditempatkan di belakang prefiks /api/v1/. Jika Caddy meneruskan permintaan /api/v1/users/profile secara mentah-mentah ke user-service, backend akan mengembalikan error 404 karena hanya mengenali rute /users/profile.
Kita harus membuang prefiks tersebut tepat sebelum permintaan dikirim melalui reverse_proxy.
Pola Implementasi Menggunakan Blok handle dan uri
#
# Contoh: API Gateway dengan Strip Prefix Dinamis
example.com {
# Amankan API Gateway dengan membatasi rute
handle /api/v1/users/* {
# 1. Hapus prefiks "/api/v1/users" sehingga "/api/v1/users/profile" menjadi "/profile"
uri strip_prefix /api/v1/users
# 2. Kirim permintaan yang sudah bersih ke user service
reverse_proxy user-service:8081
}
handle /api/v1/orders/* {
uri strip_prefix /api/v1/orders
reverse_proxy order-service:8082
}
# Penanganan rute root untuk menyajikan berkas frontend statis
handle {
root * /var/www/frontend/dist
try_files {path} /index.html
file_server
}
}
[!IMPORTANT] Gunakan blok
handleuntuk mengisolasi konfigurasi. Menggunakan direktifhandlememastikan bahwa operasiuri strip_prefixhanya berlaku di dalam cakupan blok rute tersebut. Jika kita menulisuri strip_prefixsecara global di luar blokhandle, Caddy akan memotong jalur untuk seluruh permintaan yang masuk—termasuk aset statis frontend kita—sehingga merusak seluruh visual aplikasi web kita.
Manipulasi Query String (Query String Rewriting) #
Selain mengubah bagian jalur (path), kita sering kali perlu memanipulasi query string (parameter setelah tanda tanya ? pada URL) sebelum permintaan diteruskan ke upstream backend kita.
1. Menambahkan Parameter Query Baru #
Caddy secara otomatis mempertahankan query string asli klien ketika kita melakukan rewrite. Jika kita ingin menyisipkan parameter tambahan, kita cukup menuliskannya di jalur tujuan baru dan menyertakan placeholder bawaan {query} untuk menggabungkan parameter lama.
# Contoh: Menyisipkan parameter asal (source) untuk pelacakan analitik backend
example.com {
@search_route path /search
# Jika klien meminta: /search?q=caddy
# Caddy menulis ulang menjadi: /api/search?source=gateway&q=caddy
rewrite @search_route /api/search?source=gateway&{query}
reverse_proxy api-service:8080
}
2. Mengosongkan Seluruh Parameter Query #
Jika demi alasan keamanan atau privasi kita ingin membuang semua parameter query yang dikirimkan oleh klien sebelum mencapai server backend kita, kita cukup melakukan rewrite ke jalur baru tanpa menyertakan placeholder {query}.
# Contoh: Memblokir kebocoran query parameter pada rute laporan sensitif
example.com {
@reports path /reports/download
# Jika klien mencoba mengirimkan token via query: /reports/download?token=secret123
# Caddy memotongnya secara internal menjadi: /internal/reports/download (tanpa query)
rewrite @reports /internal/reports/download
reverse_proxy backend:8080
}
Penulisan Ulang Berbasis Ekspresi Reguler (Regex Rewrite) #
Untuk skenario migrasi sistem yang kompleks, kita sering kali dihadapkan pada situasi di mana pola URL lama sangat berbeda dengan pola URL baru. Contohnya, memindahkan struktur blog lama /posts/2026/06/16/belajar-caddy ke struktur baru /blog/belajar-caddy.
Kita dapat menggunakan ekspresi reguler (regular expression atau regex) untuk menangkap bagian tertentu dari URL lama menggunakan grup penangkap (capturing groups) dan menyusunnya kembali ke URL baru.
Implementasi Menggunakan Matcher path_regexp dan Direktif rewrite
#
Caddy menggunakan pustaka regex bawaan bahasa Go yang sangat cepat dan aman dari serangan Regular Expression Denial of Service (ReDoS).
# Contoh: Migrasi URL Blog Dinamis menggunakan Regex
example.com {
# 1. Buat named matcher dengan filter regex
# Kita membuat grup penangkap (.+) untuk mengambil slug judul artikel
@old_blog_pattern {
path_regexp old_blog ^/posts/\d{4}/\d{2}/\d{2}/(.+)$
}
# 2. Lakukan rewrite menggunakan nilai dari grup penangkap
# Kita mengakses grup penangkap pertama menggunakan placeholder {re.old_blog.1}
rewrite @old_blog_pattern /blog/{re.old_blog.1}
# Hasil evaluasi internal Caddy:
# Input: /posts/2026/06/16/belajar-caddy
# Output: /blog/belajar-caddy
file_server { root /var/www/blog }
}
Penulisan Alternatif Menggunakan Direktif uri path_regexp
#
Jika kita hanya ingin melakukan manipulasi regex pada tingkat jalur tanpa menggunakan named matcher eksternal, kita bisa menggunakan direktif uri path_regexp secara langsung di dalam rute. Ini menghasilkan kode Caddyfile yang jauh lebih ringkas.
# Contoh: Regex Rewrite Ringkas dalam Satu Baris
example.com {
# Sintaks: uri path_regexp <pattern> <replacement>
# Kita menggunakan variabel substitusi $1 untuk mereferensikan grup penangkap pertama
uri path_regexp ^/posts/\d{4}/\d{2}/\d{2}/(.+)$ /blog/$1
reverse_proxy blog-service:8080
}
Logika Evaluasi Perutean di Caddy #
Untuk menghindari kesalahan konfigurasi (misconfiguration), kita harus memahami bagaimana Caddy mengevaluasi instruksi rewrite dan perutean di memori internalnya. Caddy memproses rute berdasarkan prioritas direktif yang telah ditentukan secara bawaan (directive order), bukan berdasarkan urutan penulisan baris kode di Caddyfile kita.
Secara default, Caddy mengevaluasi direktif dalam urutan berikut (dari prioritas tertinggi ke terendah):
uri(Termasuk pemotongan prefiks)rewrite(Penulisan ulang URI internal)try_files(Evaluasi keberadaan berkas fisik)reverse_proxy/file_server(Penyajian akhir konten)
Mari kita perhatikan alur pengambilan keputusan evaluasi rute ini pada diagram flowchart berikut:
flowchart TD
A["1. Request HTTP Diterima Caddy"] --> B{"2. Apakah ada direktif 'uri'?\n(e.g. strip_prefix)"}
B -- "Ya" --> C["Ubah Path di Memori\n(e.g. Hapus /api/v1)"]
B -- "Tidak" --> D{"3. Apakah ada direktif 'rewrite'?\n(e.g. rewrite regex / nama matcher)"}
C --> D
D -- "Ya" --> E["Ganti URI penuh secara internal\n(Path + Query)"]
D -- "Nu" --> F{"4. Apakah ada direktif 'try_files'?"}
E --> F
F -- "Ya" --> G{"Apakah berkas/direktori fisik ada di disk?"}
F -- "Tidak" --> H["5. Teruskan ke Handler Akhir\n(reverse_proxy atau file_server)"]
G -- "Ya" --> I["Sajikan berkas fisik\n(file_server)"]
G -- "Tidak" --> J["Lakukan rewrite ke fallback rute\n(e.g. /index.html)"]
J --> H
H --> K["6. Kirim Respons HTTP ke Klien"]Dengan memahami diagram di atas, kita tahu bahwa jika kita menuliskan rewrite dan reverse_proxy di dalam blok server yang sama tanpa pembatas rute, Caddy akan selalu menyelesaikan proses rewrite terlebih dahulu sebelum menyerahkan request ke handler reverse_proxy.
Ringkasan #
- Internal vs Eksternal: Rewrite terjadi sepenuhnya di dalam memori Caddy tanpa mengubah URL di browser klien, sedangkan Redirect mengirimkan status HTTP 3xx ke browser untuk memicu koneksi baru.
- Perbedaan Direktif: Gunakan
rewriteuntuk mengganti seluruh URI secara cepat, dan gunakanuriuntuk operasi manipulasi teks spesifik sepertistrip_prefixataureplace.- Pola Fallback SPA: Terapkan
try_files {path} {path}/ /index.htmluntuk mendukung client-side routing pada aplikasi React/Svelte/Vue secara aman.- Integrasi Microservices: Selalu gunakan
uri strip_prefixdi dalam blokhandleuntuk membuang awalan jalur perutean sebelum diteruskan kereverse_proxybackend.- Query Parameter: Caddy secara bawaan menggabungkan parameter query lama ketika kita melakukan rewrite, gunakan placeholder
{query}untuk kontrol penggabungan manual.- Ekspresi Reguler: Gunakan
path_regexpuntuk menangkap variabel URL dinamis melalui grup penangkap ($1atau{re.name.1}) guna melakukan migrasi URL tingkat lanjut.