Suspense #
Dalam pengembangan web modern, pengalaman pengguna yang cepat dan responsif menjadi sangat penting. Salah satu konsep yang mendukung hal ini adalah Suspense, terutama ketika dikombinasikan dengan streaming HTTP response. Suspense memungkinkan aplikasi web menunda rendering komponen hingga data yang dibutuhkan siap, sehingga UI tetap responsif dan tidak menunggu seluruh data selesai dimuat.
Apa itu Suspense? #
Suspense adalah mekanisme untuk menangani asynchronous rendering. Dengan Suspense, UI dapat menampilkan fallback sementara (misal loading spinner atau skeleton) hingga data atau komponen tertentu siap dirender. Suspense mendukung:
- Lazy loading komponen
- Data fetching secara asynchronous
- Font dan asset yang lambat dimuat
Tujuannya adalah memberikan perceived performance yang lebih baik dan menjaga pengalaman pengguna tetap lancar.
Mengapa Suspense Bisa Possible? #
Suspense menjadi mungkin karena beberapa fitur baik di sisi frontend/browser maupun backend:
Sisi Frontend/Browser #
- Asynchronous Rendering: Browser dan library UI modern mendukung rendering secara bertahap.
- Promise-based Data Fetching: Frontend menunggu Promise selesai untuk menggantikan fallback.
- Incremental Hydration: Komponen UI bisa di-render satu per satu saat data tersedia.
Sisi Backend #
- Streaming Response: Server dapat mengirim data secara bertahap (chunked) dalam satu endpoint.
- Partial Data Availability: Server tidak harus menunggu seluruh proses data selesai sebelum mengirim ke client.
- Non-blocking I/O: Backend modern (Go, Node.js, dll.) memungkinkan operasi I/O asynchronous yang mendukung streaming data real-time.
Dampak dari Kemungkinan Ini #
- UX Lebih Lancar: Pengguna melihat konten muncul lebih cepat.
- Penggunaan Resource Efisien: Browser dan server tidak perlu menahan seluruh data sekaligus.
- Perceived Performance Tinggi: Tampilan UI responsif meningkatkan kepuasan pengguna.
- Prioritas Konten: Bagian paling penting bisa tampil lebih dulu.
- Arsitektur Modular: Memudahkan pemisahan logika rendering dan data fetching.
Bagaimana Streaming HTTP Response Bekerja (Single Endpoint) #
Client Request ----------------------+
|
v
Server mulai mengirim HTML + chunked content
|--- Header HTML (langsung dikirim) ---> Client render header
|--- <template id="content">Placeholder</template> (dikirim) ---> Client render fallback
|--- Content data ready (chunked) ---> Client ganti placeholder template dengan data sebenarnya
|--- Footer HTML dikirim (langsung render)
Purpose / Tujuan #
- UX Lebih Lancar: Tidak ada tampilan blank page, hanya placeholder sementara.
- Optimisasi Rendering: Hanya bagian UI yang siap dirender, mengurangi blocking.
- Prioritas Konten: Bagian penting dapat tampil lebih dulu.
- Mendukung Data Asynchronous: Mudah integrasi dengan fetch API, GraphQL, atau server-side streaming.
Nilai Penting #
- Responsiveness: Pengguna merasa aplikasi lebih cepat.
- User Retention: Tampilan loading yang halus mengurangi bounce.
- Maintainable Code: Menangani asynchronous data fetching dengan cara deklaratif.
Contoh Implementasi Backend Golang + Frontend Alpine.js (Single Endpoint) #
Berikut contoh layout header, content, footer, di mana content dimuat terakhir melalui streaming dalam satu endpoint.
package main
import (
"fmt"
"net/http"
"time"
)
func streamHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Transfer-Encoding", "chunked")
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
return
}
// Kirim header HTML
fmt.Fprintf(w, `<div id="header">Header Static</div>
<div id="content" x-data="{loaded: false, content: ''}">
<template x-if="!loaded">
<div>Loading content...</div>
</template>
<template x-if="loaded">
<div x-text="content"></div>
</template>
</div>
<div id="footer">Footer Static</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
<script>
function updateContent(data) {
let el = document.querySelector('#content');
el.__x.$data.content = data;
el.__x.$data.loaded = true;
}
</script>
`)
flusher.Flush()
// Simulasi content lama
contentChunks := []string{"Bagian konten 1\n", "Bagian konten 2\n", "Bagian konten 3\n"}
fullContent := ""
for _, chunk := range contentChunks {
fullContent += chunk
time.Sleep(2 * time.Second) // simulasi logic lambat
}
// Kirim script untuk mengganti placeholder dengan content final
fmt.Fprintf(w, `<script>updateContent(%q);</script>`, fullContent)
flusher.Flush()
}
func main() {
http.HandleFunc("/", streamHandler)
fmt.Println("Server berjalan di :8080")
http.ListenAndServe(":8080", nil)
}
Penjelasan #
- Semua layout (header, placeholder content, footer) dikirim dalam satu response.
- Placeholder content muncul segera.
- Setelah content siap, server mengirimkan
<script>untuk mengganti placeholder dengan konten final. - Tidak ada request tambahan, meniru mekanisme Suspense React-style.
Pros dan Cons #
Pros:
- UX lebih responsif karena bagian statis langsung tampil.
- Single endpoint, lebih efisien.
- Modular dan mudah dipelihara.
Cons:
- Streaming dan async logic lebih kompleks.
- Debugging lebih sulit.
- Memerlukan browser/frontend yang mendukung partial DOM update atau script execution.
Best Practice #
- Gunakan fallback (spinner/skeleton) agar UX konsisten.
- Prioritaskan konten statis dan penting lebih dulu.
- Gunakan streaming HTTP jika backend mendukung.
- Error handling di frontend dengan Alpine.js.
- Monitoring performa streaming untuk menghindari slow fallback.
–
Kesimpulan #
Dengan satu endpoint streaming HTML, kita bisa menerapkan Suspense-style loading di web modern. Header dan footer muncul segera, sementara content yang membutuhkan proses lebih lama diganti secara otomatis melalui script. Pendekatan ini meningkatkan perceived performance dan UX tanpa memerlukan multiple request.