Deployment
Build, jalankan, dan deploy aplikasi Bosia di produksi — di Bun atau Cloudflare Workers.
Build Produksi
bun run buildPerintah ini menghasilkan direktori dist/ yang berisi:
dist/server/— entry point serverdist/client/— bundle JavaScript dan CSS kliendist/prerendered/— HTML statis untuk rute yang telah di-prerender
Menjalankan di Produksi
bun run startAtau secara langsung:
bun dist/server/index.jsAtur port dengan variabel lingkungan PORT (default: 9000).
Health Check
Bosia mengekspos endpoint health di /_health:
curl http://localhost:9000/_health{ "status": "ok", "timestamp": 1711360000000, "timezone": "UTC" }Prerendering
Tandai rute untuk prerendering statis:
// +page.server.ts
export const prerender = true;Halaman yang di-prerender dibuat sebagai HTML statis selama bosia build dan disajikan dari dist/prerendered/ dengan cache header 1 jam.
Payload data untuk navigasi sisi klien juga di-prerender sebagai file JSON di dist/prerendered/__bosia/data/<route>.json. Ini berarti navigasi klien berfungsi di situs statis (GitHub Pages, Netlify, dll.) tanpa server yang berjalan.
Jika aplikasi me-prerender minimal satu route, build juga menyimpan halaman 404-nya sebagai dist/static/404.html, sehingga host statis menjawab path yang tidak dikenal dengan 404 sungguhan, bukan halaman beranda. Untuk deploy Cloudflare statis saja (assets, tanpa worker), set "not_found_handling": "404-page"; dengan --target=workers worker yang menjawab path tak dikenal, jadi file ini tidak dibuat. Dengan setting itu, build gagal jika 404.html tidak bisa dibuat (path tak dikenal tidak menjawab 404), kecuali ada public/404.html; selain itu build hanya memberi peringatan. 404.html tetap dibuat walau tidak ada route yang di-prerender. File public/404.html akan menggantikannya.
Caching Aset Statis
Bosia mengatur cache header secara otomatis:
| Tipe Aset | Cache Header |
|---|---|
| Nama file dengan hash | public, max-age=31536000, immutable |
| File tanpa hash | no-cache |
Setiap file statis dan halaman prerender juga membawa ETag. Saat browser mengecek ulang file no-cache dan tidak ada yang berubah, Bosia membalas 304 Not Modified tanpa body.
Kompresi
Bosia mengompresi response sendiri — tanpa perlu setup reverse proxy:
- Halaman dan JSON dikompresi per request, brotli bila browser mendukung, jika tidak gzip. Body di bawah 2KB dikirim apa adanya. Response tanpa cache memakai level brotli yang cepat; halaman di response cache dikompresi sekali dengan level lebih kuat.
- JS/CSS client dan halaman prerender dikompresi sekali saat build (
.br+.gzdi samping tiap file didist/) lalu dikirim siap pakai. - File
public/dikirim apa adanya. Gambar sudah terkompresi.
Proxy di depan (nginx, Caddy) meneruskan response terkompresi tanpa mengubahnya. Di Cloudflare Workers, Cloudflare yang mengompresi dan file .br/.gz tidak dibuat.
File Statis di Memori
Setelah request pertama, Bosia menyimpan file statis kecil dan halaman prerender (beserta versi .br/.gz-nya) di memori, sehingga request berikutnya tidak perlu membaca disk. Tidak ada yang dimuat saat startup, jadi cold start tidak lebih lambat.
ASSET_CACHE_MAX_FILE_BYTES(default 1MB): file yang lebih besar selalu dibaca dari disk.ASSET_CACHE_MAX_BYTES(default 50MB): batas total. Jika penuh, file yang paling lama tidak dipakai dibuang.0mematikannya.- Range request (seek video, lanjut unduh) selalu dibaca dari disk.
Di Cloudflare Workers ini tidak berpengaruh: Cloudflare menyajikan file statis sebelum worker berjalan.
Di Belakang Reverse Proxy
Saat Bosia berjalan di belakang nginx, Caddy, Cloudflare, ALB, atau reverse proxy / load balancer lain, host publik biasanya berbeda dengan header Host yang sampai ke proses Bun di dalamnya. Setel:
TRUST_PROXY=trueagar pemeriksaan origin CSRF menghormati X-Forwarded-Host dan X-Forwarded-Proto serta menerima request yang Origin-nya cocok dengan URL publik.
Aktifkan TRUST_PROXY=true hanya jika:
- Ada proxy atau load balancer di depan Bosia, dan
- Proxy tersebut membersihkan setiap header
X-Forwarded-*yang dikirim klien sebelum diteruskan (pastikan perilaku proxy-mu), dan - Proxy menyuntikkan
X-Forwarded-Host/X-Forwarded-Protosendiri yang mencerminkan origin publik.
Jangan menyetel TRUST_PROXY=true jika:
- Bosia langsung terekspos ke internet tanpa proxy, atau
- Kamu tidak bisa memastikan proxy membersihkan header
X-Forwarded-*yang masuk — ini akan membuat klien mana pun bisa memalsukan origin-nya sendiri dan melewati CSRF.
Lihat Keamanan › Deployment di belakang reverse-proxy untuk penjelasan lengkapnya.
Memasang di Sub-Path
Untuk menyajikan aplikasi dari example.com/sso, bukan dari akar origin — beberapa aplikasi berbagi satu hostname, atau subdomain tidak tersedia — setel:
BASE_PATH=/ssonginx lalu meneruskan path apa adanya. Tanpa stripping, tanpa proxy_redirect, tanpa sub_filter:
location /sso {
proxy_pass http://127.0.0.1:9000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}Perhatikan location /sso tanpa garis miring di akhir. location /sso/ tidak akan cocok dengan example.com/sso polos, yang oleh Bosia disajikan sebagai halaman akar aplikasi.
Bosia melepas prefix sekali, di tepi permintaan, sehingga semua yang di belakangnya bekerja di "ruang aplikasi" — hooks, load(), actions, dan event.url.pathname tidak pernah melihat prefix, dan rute ditulis persis seperti di akar. Saat keluar, prefix dipasang kembali: redirect, kanonikalisasi garis miring akhir, URL aset framework, entri history router klien, dan permintaan datanya. Cookie memakai mount sebagai path bawaannya, jadi aplikasi tetangga di origin yang sama tidak pernah menerimanya.
Permintaan di luar base menghasilkan 404 — termasuk health check, yang pindah ke /sso/_health. Perbarui load balancer, HEALTHCHECK Docker, atau probe Kubernetes agar sesuai.
Yang tidak tercakup
URL yang dibangun komponen Anda. Bosia menulis ulang URL di HTML yang dirender server, tetapi klien merender ulang saat mount dan komponen menuliskan kembali path aslinya. Apa pun yang disusun komponen memerlukan ekspor base:
<script>
import { base } from "bosia";
</script>
<img src="{base}/logo.png" alt="" />
<span style="mask-image:url('{base}/icons/check.svg')"></span>Ini mudah terlewat karena gagal tanpa suara — mask yang 404 tampak sebagai kotak kosong, bukan galat.
Markup statis sudah ditangani: literal <a href="/masuk"> di berkas .svelte ditulis ulang saat kompilasi, jadi href di DOM adalah URL sebenarnya. Tidak ada yang ditulis ulang setelah klik — tabel rute dibuat dengan prefix, jadi klik mendorong persis URL yang ada di tautan. Karena itu goto() menerima path sebenarnya:
goto(`${base}/beranda`); // bukan goto("/beranda")redirect() tidak memerlukannya, baik di load() maupun di action — sudah ditulis ulang untuk Anda:
throw redirect(303, "/masuk"); // → /sso/masuk di kabelBegitu pula event.url.pathname, yang sudah berada di ruang aplikasi saat Anda membacanya. Gunakan base hanya saat menyusun URL absolut secara manual, misalnya ${url.origin}${base}/reset?t=... untuk tautan yang keluar dari aplikasi.
Build dan runtime harus sepakat. CSS terkompilasi, tabel rute klien, dan markup di berkas .svelte Anda semuanya ditulis ulang saat build, jadi BASE_PATH harus disetel untuk bosia build maupun bosia start. Build mencatat nilainya ke dist/manifest.json dan server memperingatkan saat start jika berbeda:
⚠️ Built for BASE_PATH="/sso" but running with "" — CSS urls and the client route table are baked in and will not match.Tanggapi peringatan itu dengan serius: ketidakcocokannya sendiri senyap — font hilang, ikon kosong, atau tautan yang keluar dari aplikasi. Jika BASE_PATH berada di .env.production, ingat bahwa hanya bosia build dan bosia start yang memuatnya; menjalankan server langsung dengan bun run tidak.
Graceful Shutdown
Server produksi menangani sinyal SIGTERM dan SIGINT:
- Berhenti menerima koneksi baru
- Menunggu request yang sedang berjalan untuk selesai
- Memaksa keluar setelah 10 detik jika proses shutdown terhenti
Docker
Contoh Dockerfile:
FROM oven/bun:1 AS base
WORKDIR /app
# Install dependencies
FROM base AS deps
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
# Build
FROM deps AS build
COPY . .
RUN bun run build
# Production
FROM base AS runtime
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/package.json ./
ENV NODE_ENV=production
ENV PORT=9000
EXPOSE 9000
CMD ["bun", "dist/server/index.js"]Cloudflare Workers
Bosia juga bisa di-build untuk Cloudflare Workers, termasuk paket gratisnya. Pilih target lewat command line:
bosia build --target=workersatau sekali saja, di bosia.config.ts:
import { defineConfig } from "bosia";
export default defineConfig({ target: "workers" });Selain dist/ seperti biasa, build Workers menulis:
dist/worker/index.js— worker-nya. File statis dan halaman prerender didist/static/dilayani Cloudflare sebelum worker berjalan.wrangler.jsonc— hanya jika kamu belum punya. Setelah itu file ini milikmu: tambahkan binding (D1, KV, R2) danvarsdi sana. Build ulang tidak pernah menimpanya.
Coba secara lokal dengan bosia start — untuk build Workers perintah ini menjalankan wrangler dev (Wrangler diunduh saat pertama dipakai). Deploy dengan:
bunx wrangler deployBinding — `event.platform.env`
Cloudflare memberikan binding ke worker, bukan ke variabel global. Bosia meneruskannya ke kode server sebagai event.platform.env — di hooks, +server.ts, load() dan metadata():
// src/routes/posts/+page.server.ts
import type { LoadEvent } from "bosia";
export async function load({ platform }: LoadEvent) {
const { results } = await platform!.env.DB.prepare("SELECT * FROM posts").all();
return { posts: results };
}platform bernilai undefined saat app yang sama berjalan di Bun. Untuk memberi tipe pada binding, jalankan bunx wrangler types lalu perluas PlatformEnv milik Bosia dengan Env yang dihasilkan:
// src/platform.d.ts — file tersendiri: `export {}` membuat ini memperluas "bosia", bukan menggantinya
export {};
declare module "bosia" {
interface PlatformEnv extends Env {}
}Variabel lingkungan
File .env tidak ikut di-deploy. Nilai runtime datang dari Cloudflare: vars di wrangler.jsonc, wrangler secret put untuk rahasia, dan file .dev.vars untuk wrangler dev lokal. Nilainya sampai ke $env dan process.env seperti biasa. Nama variabel tetap berasal dari file .env*, dan STATIC_* / PUBLIC_STATIC_* tetap ditanam saat build.
Variabel framework bekerja sama (BODY_SIZE_LIMIT, CSRF_*, CORS_*, CACHE_*). Yang berkaitan dengan menjalankan proses — PORT, IDLE_TIMEOUT, BOSIA_REUSE_PORT — tidak berpengaruh di Workers.
Kode khusus Bun
Worker tidak punya global Bun dan tidak punya filesystem. Build berhenti lebih awal jika kode server — routes, hooks.server.ts dan src/lib/server/ — memakai Bun.* atau meng-import fs, bun atau bun:*, lalu menampilkan tiap file dan barisnya. Ganti dengan Web API (fetch, crypto.subtle) atau binding (D1 alih-alih bun:sqlite, R2 alih-alih Bun.s3). node:crypto, node:zlib dan sejenisnya berjalan lewat kompatibilitas Node milik Cloudflare.
Pengecekan typeof Bun di baris yang sama diizinkan, jadi kode yang dipakai bersama kedua target bisa bercabang. Pemeriksaan ini membaca file berdasarkan path, jadi panggilan Bun di helper biasa src/lib/*.ts lolos — dan baru gagal saat request. Set BOSIA_WORKERS_GUARD=0 untuk mengubah error menjadi peringatan.
Prerender halaman yang tidak berubah
Halaman yang di-prerender adalah file biasa yang disajikan Cloudflare sebelum worker berjalan. Tidak memakai waktu CPU dan tidak dihitung sebagai request worker, yang penting di paket gratis (100.000 request per hari). Halaman landing, harga, dan dokumentasi cocok untuk ini:
// src/routes/pricing/+page.server.ts
export const prerender = true;Loader-nya berjalan sekali, saat build, jadi jangan dipakai untuk halaman yang bergantung pada pengunjung (cookie, user yang login), membaca binding event.platform.env, atau mengekspor form actions — build membiarkan halaman itu tetap live.
Batasan
- Response cache per isolate. Tetap membantu di route yang ramai, tapi Cloudflare menjalankan banyak isolate dan membuangnya sesukanya, jadi
invalidate()hanya membersihkan salinan di isolate yang menjalankannya. Buat halaman ter-cache berumur pendek, atau keluarkan route denganexport const cache = false. - Ukuran bundle. Paket gratis mengizinkan 3 MB setelah gzip. Aplikasi demo sekitar 185 KB. Plugin yang di-import
bosia.config.tsikut di-bundle meski hanya aktif di dev, tapi compiler Svelte yang dipakainya tidak ikut. - Tanpa graceful shutdown — siklus hidup diatur Cloudflare.
/_healthtetap menjawab. - Pemanasan saat startup. Tiap isolate baru merender
/sekali, tanpa hooks, loader, atau cache, agar request pertama tidak menanggung biaya kompilasi. Kode di bagian atas komponen/berjalan satu kali ekstra per isolate. - Kompresi dilakukan Cloudflare, bukan Bosia, jadi tidak memakan waktu CPU Anda.
Hosting Sandbox / Multi-Tenant
Jika kamu menjalankan app Bosia di dalam sandbox yang memblokir addon native (.node) — misalnya host multi-tenant yang memindai node_modules tiap app — perlu diketahui bahwa toolchain Bosia membawa binari native yang dibutuhkan untuk build dan menjalankan app:
| Paket | Dipakai oleh |
|---|---|
@tailwindcss/oxide |
Mesin CSS Tailwind v4 (build) |
lightningcss |
Transform/minify CSS (build) |
@parcel/watcher |
Pemantau file di bosia dev (mode dev) |
Ini adalah perkakas build framework — murni komputasi / inotify — dan tidak butuh syscall pengubah-kernel yang biasanya diblokir sandbox semacam itu (unit systemd SystemCallFilter=@system-service mengizinkannya). Jika host-mu menolak semua file .node, izinkan (allowlist) paket-paket ini (beserta sub-paket per-platform seperti lightningcss-linux-x64-musl), bukan kode app itu sendiri. Cocokkan nama direktori paket dengan akhiran platform yang ter-anchor agar tiruan seperti lightningcss-evil tidak lolos.
Variabel Lingkungan
Lihat Variabel Lingkungan untuk daftar lengkap opsi konfigurasi termasuk PORT, BODY_SIZE_LIMIT, CORS, dan pengaturan CSRF.