Rewrite

Rewrite #

URL rewriting adalah teknik mengubah URL dari sisi server sebelum request diproses lebih lanjut. Berbeda dari redirect (yang memberitahu browser untuk membuka URL lain), rewrite terjadi secara internal — browser dan client tidak mengetahui bahwa URL sudah diubah. Mereka tetap melihat URL asli yang mereka minta.

Caddy menyediakan dua cara utama untuk rewrite URL: direktif rewrite yang merupakan shortcut sederhana, dan direktif uri yang memberikan kontrol lebih granular.

Perbedaan Rewrite vs Redirect #

Redirect (eksternal):
  Client → GET /lama HTTP/1.1
  Server → 301 Moved Permanently
           Location: /baru
  Client → GET /baru HTTP/1.1 (request baru!)
  Server → 200 OK
  
  Terlihat di browser: URL berubah ke /baru
  Digunakan untuk: URL yang berubah permanen, SEO canonical URL

Rewrite (internal):
  Client → GET /lama HTTP/1.1
  Caddy → ubah path secara internal menjadi /baru
  Backend → menerima request sebagai /baru
  Client → menerima response 200 OK
  
  Terlihat di browser: URL tetap /lama
  Digunakan untuk: clean URL, SPA routing, legacy path support

Direktif rewrite #

Direktif rewrite mengubah URI request (path + query string):

example.com {
    # Rewrite path statis
    rewrite /old-path /new-path
    
    # Rewrite dengan query string
    rewrite /search /api/search?q={query}
    
    # Rewrite semua request ke satu file (SPA pattern)
    rewrite * /index.html
    
    file_server { root /var/www/html }
}

Direktif uri #

Direktif uri memberikan kontrol lebih spesifik terhadap bagian URI:

example.com {
    # Strip prefix dari path
    uri strip_prefix /api/v1
    # /api/v1/users → /users
    
    # Strip suffix dari path
    uri strip_suffix .html
    # /about.html → /about
    
    # Tambah prefix
    uri path_regexp (.*) /app$1
    # /users → /app/users
    
    # Ganti bagian path dengan regex
    uri path_regexp ^/blog/(.*)$ /posts/$1
    # /blog/hello-world → /posts/hello-world
    
    reverse_proxy backend:3000
}

Rewrite untuk Single Page Application (SPA) #

Ini adalah salah satu use case paling umum — React, Vue, Angular semua memerlukan semua route diarahkan ke index.html:

app.example.com {
    root * /var/www/app
    
    # Jika file atau direktori ada, sajikan langsung (aset statis)
    # Jika tidak ada, arahkan ke index.html (SPA routing)
    try_files {path} /index.html
    
    file_server
}

Dengan rewrite secara Eksplisit #

app.example.com {
    root * /var/www/app
    
    # Jangan rewrite untuk file statis yang ada
    @static {
        file {path}
        path *.js *.css *.png *.jpg *.svg *.ico *.woff2 *.map
    }
    
    # Rewrite semua route non-statis ke index.html
    @spa {
        not file {path}
        not path /api/*
    }
    rewrite @spa /index.html
    
    reverse_proxy /api/* backend:3000
    
    file_server
}

Strip Prefix sebelum Reverse Proxy #

Sangat umum di deployment di mana beberapa service di-host di sub-path:

example.com {
    # /api/v1/* dikirim ke backend sebagai /*
    handle /api/v1/* {
        uri strip_prefix /api/v1
        reverse_proxy api-backend:8080
    }
    
    # /admin/* dikirim ke admin service sebagai /*
    handle /admin/* {
        uri strip_prefix /admin
        reverse_proxy admin-backend:9000
    }
    
    # Root: static files
    handle {
        root * /var/www/html
        file_server
    }
}

Rewrite dengan Regex — Transformasi Path yang Kompleks #

example.com {
    # Rewrite format URL lama ke format baru
    # /posts/2024/01/15/hello-world → /blog/hello-world
    rewrite * {re.1} {
        path_regexp ^/posts/\d{4}/\d{2}/\d{2}/(.+)$ /blog/{re.1}
    }
    
    # Atau dengan uri path_regexp
    uri path_regexp ^/posts/\d{4}/\d{2}/\d{2}/(.+)$ /blog/$1
    
    root * /var/www/html
    file_server
}

Rewrite Query String #

example.com {
    # Tambah query parameter
    @search path /search
    rewrite @search /search?engine=caddy&{query}
    
    # Hapus query string tertentu
    # (lebih mudah dengan uri menggunakan placeholder)
    
    reverse_proxy backend:3000
}

try_files — Fallback Pattern #

try_files adalah cara elegan untuk mencoba beberapa path secara berurutan:

example.com {
    root * /var/www/html
    
    # Coba: file asli → file dengan .html → index.html
    try_files {path} {path}.html /index.html
    
    # Contoh:
    # /about          → coba /about, lalu /about.html, lalu /index.html
    # /contact.html   → coba /contact.html (langsung ada)
    # /dashboard      → tidak ada, /dashboard.html tidak ada, fallback /index.html
    
    file_server
}

Rewrite Berdasarkan Header atau Query Parameter #

example.com {
    # Rewrite berdasarkan versi API dari header
    @v1 header X-API-Version "1"
    @v2 header X-API-Version "2"
    
    rewrite @v1 /v1{uri}
    rewrite @v2 /v2{uri}
    
    reverse_proxy backend:3000
}

# Atau berdasarkan query parameter
example.com {
    @mobile query platform=mobile
    rewrite @mobile /mobile{path}
    
    file_server { root /var/www }
}

Rewrite untuk Legacy URL Support #

Ketika melakukan migrasi platform, pertahankan URL lama tetap berfungsi:

example.com {
    # URL format lama WordPress
    # /2024/01/hello-world/ → /blog/hello-world
    uri path_regexp ^/(\d{4})/(\d{2})/([^/]+)/?$ /blog/$3
    
    # Format produk lama
    # /product.php?id=123 → /products/123
    @old-product {
        path /product.php
        query id=*
    }
    rewrite @old-product /products/{query.id}
    
    # Index lama
    rewrite /index.php /
    rewrite /home.html /
    
    reverse_proxy backend:3000
}

Urutan Eksekusi Rewrite #

example.com {
    # PENTING: Rewrite dieksekusi dalam urutan yang ditentukan
    # Setelah rewrite, directive berikutnya menggunakan path BARU
    
    # Step 1: Strip prefix
    handle /api/* {
        uri strip_prefix /api
        
        # Step 2: Setelah strip, path adalah /users bukan /api/users
        # Ini dikirim ke backend
        reverse_proxy backend:8080
    }
}

Rewrite untuk Versioning API #

api.example.com {
    # Jika tidak ada versi di path, tambahkan /v2 sebagai default
    @no-version {
        path /users* /products* /orders*
        not path /v1/* /v2/* /v3/*
    }
    rewrite @no-version /v2{uri}
    
    # Caddy sekarang melihat path /v2/users, /v2/products, dll.
    # Backend bisa membedakan versi API
    reverse_proxy backend:8080
}

Rewrite untuk Pretty URL #

example.com {
    root * /var/www/html
    
    # Izinkan akses /artikel/judul-artikel
    # Yang sebenarnya ada di /artikel.php?slug=judul-artikel
    @pretty path_regexp ^/artikel/([^/]+)/?$
    rewrite @pretty /artikel.php?slug={re.1}
    
    # Atau lebih umum: /[kategori]/[slug] → /index.php?cat=[kategori]&slug=[slug]
    @cat-slug path_regexp ^/([^/]+)/([^/]+)/?$
    rewrite @cat-slug /index.php?cat={re.1}&slug={re.2}
    
    # Aktifkan PHP FastCGI setelah rewrite
    php_fastcgi unix//run/php/php8.3-fpm.sock
    file_server
}

Ringkasan #

  • rewrite mengubah URI secara internal — browser tidak melihat perubahan ini, berbeda dari redirect yang mengubah URL di browser.
  • uri strip_prefix /api adalah pola yang paling umum saat routing ke backend — hapus prefix path agar backend tidak perlu mengetahui path mounting-nya.
  • try_files {path} /index.html adalah konfigurasi SPA (React/Vue/Angular) yang paling umum — sajikan file statis jika ada, fallback ke index.html untuk client-side routing.
  • Gunakan uri path_regexp untuk rewrite yang kompleks melibatkan pattern matching dan capture groups.
  • Rewrite dan redirect bisa dikombinasikan: rewrite dulu secara internal, lalu jika perlu redirect ke URL canonical.
  • Urutan directive penting — rewrite mengubah path, dan handler berikutnya menggunakan path yang sudah diubah.

← Sebelumnya: CORS   Berikutnya: Redirect →

Rewrite dan Canonical URL #

Dalam konteks SEO, rewrite bisa digunakan untuk memastikan satu URL canonical yang konsisten:

example.com {
    root * /var/www/html
    
    # Hapus index.html dari URL (canonical: /about bukan /about/index.html)
    @index-html path_regexp /index\.html$
    rewrite @index-html {re.0}
    uri path_regexp /index\.html$ /
    
    # Hapus .html dari URL (canonical: /about bukan /about.html)
    @dot-html path_regexp ^(.+)\.html$
    rewrite @dot-html {re.1}
    
    # Tapi file .html tetap harus ada di disk
    try_files {path} {path}.html /index.html
    
    file_server
}

Dengan konfigurasi ini, /about.html secara internal direwrite ke /about — browser melihat URL bersih, tapi Caddy tetap bisa menemukan file about.html di disk.

Rewrite Berdasarkan Ketersediaan File #

Pola file dalam matcher memungkinkan rewrite kondisional berdasarkan apakah file tersebut benar-benar ada di disk:

example.com {
    root * /var/www/html
    
    # Jika file fisik ada, sajikan langsung
    # Jika tidak ada, teruskan ke backend (API atau SPA handler)
    @file_exists file {path}
    
    handle @file_exists {
        file_server
    }
    
    handle {
        # Tidak ada file fisik — teruskan ke backend
        reverse_proxy backend:3000
    }
}

Ini adalah pola yang sangat umum untuk aplikasi hybrid: aset statis (JS, CSS, gambar) disajikan langsung oleh Caddy, sementara route aplikasi diteruskan ke backend.

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact