15 poin oleh GN⁺ 2025-06-02 | 1 komentar | Bagikan ke WhatsApp
  • Seperti JPEG progresif, data JSON juga dapat dikirim lebih dulu dalam keadaan belum lengkap sehingga klien bisa memanfaatkan keseluruhan isi secara bertahap
  • Metode parsing JSON yang ada saat ini memiliki masalah inefisiensi karena tidak bisa melakukan apa pun sebelum seluruh data diterima sepenuhnya
  • Data dibagi menjadi beberapa chunk dengan pendekatan breadth-first, lalu bagian yang belum siap ditandai sebagai Promise dan diisi secara progresif saat sudah tersedia, sehingga klien tetap bisa memanfaatkan data yang belum lengkap
  • Konsep ini adalah inovasi inti dari React Server Components(RSC), dan <Suspense> digunakan untuk mengontrol status loading bertahap yang memang diinginkan
  • Dengan memisahkan streaming data dan alur loading UI yang disengaja, pengalaman pengguna yang lebih fleksibel dapat diberikan

Ide JPEG Progresif dan JSON Progresif

  • JPEG progresif tidak memuat gambar sekaligus dari atas ke bawah, melainkan menampilkan keseluruhan gambar lebih dulu dalam keadaan buram lalu secara bertahap menjadi tajam
  • Dengan cara serupa, pendekatan progresif dapat diterapkan pada pengiriman JSON sehingga sebagian data bisa langsung digunakan tanpa menunggu semuanya selesai
  • Pada struktur data JSON contoh, pendekatan biasa hanya bisa melakukan parsing setelah seluruh byte terakhir diterima
  • Akibatnya, klien harus menunggu sampai semuanya terkirim, termasuk bagian server yang lambat (misalnya mengambil comments dari DB yang lambat), dan inilah standar saat ini yang sangat tidak efisien

Keterbatasan parser JSON streaming

  • Dengan memperkenalkan parser JSON streaming, kita bisa membuat tree objek data yang belum lengkap (di tengah proses)
  • Namun, jika field dalam tiap objek (misalnya footer, daftar comment yang banyak, dan sebagainya) hanya terkirim sebagian, muncul masalah ketidakcocokan tipe dan sulitnya mengetahui apakah data sudah lengkap, sehingga kegunaannya menurun
  • Seperti rendering HTML streaming, pemrosesan stream secara berurutan tetap menimbulkan masalah bahwa satu bagian yang lambat menunda seluruh hasil
  • Inilah alasan mengapa JSON streaming umumnya jarang digunakan

Usulan struktur Progressive JSON

  • Alih-alih streaming depth-first seperti cara lama (yakni mengirim sambil menelusuri hingga ke bagian bawah struktur tree), digunakan pendekatan breadth-first
  • Hanya objek tingkat atas yang dikirim lebih dulu, sedangkan nilai-nilai di bawahnya dibiarkan sebagai placeholder mirip Promise, lalu diisi satu per satu dalam chunk terpisah saat sudah siap
  • Misalnya, setiap kali server selesai memuat data secara asinkron, chunk yang sesuai dikirim, dan klien dapat memanfaatkannya sejauh yang sudah siap
  • Ini memungkinkan penerimaan data asinkron (load lebih awal), sehingga tidak perlu menunggu seluruh bagian lambat selesai diproses
  • Jika klien dibangun tangguh terhadap penerimaan chunk yang tidak berurutan maupun sebagian berurutan, server bisa lebih fleksibel menerapkan berbagai strategi pembagian chunk

Inlining dan Outlining: pengiriman data yang efisien

  • Format streaming JSON progresif dapat mengekstrak objek yang digunakan ulang (misalnya userInfo yang sama direferensikan di banyak tempat) ke satu chunk terpisah tanpa duplikasi penyimpanan, lalu setiap posisi dapat merujuk ke referensi yang sama
  • Hanya bagian yang lambat yang dipisahkan dan dikirim sebagai placeholder, sementara sisanya langsung diisi untuk membentuk stream data yang efisien
  • Jika objek yang sama muncul berkali-kali, objek itu cukup dikirim sekali lalu digunakan kembali (Outlining)
  • Dengan cara ini, referensi siklik (struktur objek yang merujuk ke dirinya sendiri) juga tidak lagi sulit seperti pada JSON biasa, dan dapat diserialisasi secara alami melalui struktur referensi tidak langsung antar-chunk

Implementasi streaming progresif di React Server Components(RSC)

  • React Server Components yang sebenarnya adalah contoh representatif yang menerapkan model streaming JSON progresif
    • Server menggunakan struktur yang memuat data eksternal (misalnya Post, Comments) secara asinkron
    • Di sisi klien, bagian yang belum tiba diperlakukan sebagai Promise, lalu UI dirender secara progresif sesuai urutan kesiapan data
  • React <Suspense> digunakan untuk menetapkan status loading yang disengaja
    • Untuk mencegah lompatan tampilan yang tidak perlu dari sudut pandang pengalaman pengguna, status Promise (lubang) tidak langsung ditampilkan, melainkan loading bertahap dapat diarahkan dengan fallback <Suspense>
    • Sekalipun data tiba dengan cepat, pengembang tetap dapat mengontrol agar UI yang sebenarnya ditampilkan secara progresif sesuai tahapan yang dirancang

Ringkasan dan implikasi

  • Inovasi inti React Server Components adalah men-streaming props pada tree komponen secara progresif dari bagian luar ke dalam
  • Karena itu, tidak perlu menunggu server menyiapkan seluruh data sepenuhnya; bagian utama bisa ditampilkan lebih dulu secara bertahap, dan status tunggu loading juga dapat dikontrol dengan rinci
  • Bukan hanya streaming itu sendiri, tetapi juga dukungan struktural seperti model pemrograman yang memanfaatkannya (misalnya React <Suspense>) diperlukan
  • Melalui ini, hambatan pada metode pengiriman lama—seperti satu bagian data lambat yang menunda keseluruhan—dapat dikurangi

1 komentar

 
GN⁺ 2025-06-02
Komentar Hacker News
  • Tampaknya sebagian orang menafsirkan tulisan ini terlalu harfiah, jadi perlu dijelaskan bahwa Dan Abramov tidak sedang mengusulkan format baru bernama Progressive JSON
    • Tulisan ini membahas ide React Server Components dan proses merepresentasikan tree komponen dalam bentuk objek JavaScript lalu mengirimkannya sebagai stream
    • Pendekatan ini memungkinkan adanya "lubang" dalam tree komponen React, sehingga awalnya bisa menampilkan status loading atau skeleton UI, lalu merender penuh bagian tersebut saat data asli diterima dari server
    • Dengan cara ini, indikator loading yang lebih rinci dan tampilan awal yang cepat jadi memungkinkan
  • Menurut saya, tidak masalah jika orang mengembangkan ide ini dan menerapkannya ke pendekatan lain
    • Maksudnya adalah menjelaskan cara serialisasi data RSC bukan hanya terbatas pada React, tetapi sebagai pola yang lebih umum
    • Harapannya, berbagai ide yang ditemukan di React Server Components bisa menyerap ke teknologi atau ekosistem lain juga
  • Saya pribadi kurang suka pendekatan progressive loading, terutama pengalaman ketika konten terus bergerak atau meloncat
    • Pola yang menampilkan UI kosong saat loading terasa sangat mengganggu
  • Saat masih memakai Ember belum lama ini, ada pendekatan serupa, dan saya ingat menulis endpoint Ajax sangat menyakitkan
    • Sepertinya tujuannya adalah memindahkan ulang struktur tree agar sebagian elemen anak berada di akhir file, sehingga pemrosesan DAG (graf asiklik terarah) jadi lebih efisien
    • Jika memakai parser streaming bergaya SAX, painting bisa dimulai lebih dulu saat data datang sebagian
    • Tetapi pada VM single-thread, jika urutan kerja dirancang buruk, risikonya justru memperbesar masalah
  • Saya sudah benar-benar memakai pendekatan streaming partial JSON (Progressive JSON) dalam koneksi dengan tool AI
    • Dari pengalaman nyata, ini bukan hanya berguna untuk RSC tetapi juga untuk banyak kasus lain, dan bernilai secara praktis baik di sisi klien maupun server
  • Saya mengikuti presentasi Dan tentang "2 computers" dan posting terbaru terkait RSC
    • Dan adalah salah satu penjelas terbaik di ekosistem React, tetapi jika teknologi harus dijelaskan serumit ini
      1. entah memang teknologi yang tidak benar-benar perlu, atau
      2. ada masalah pada abstraksinya
    • Sebagian besar developer frontend masih belum benar-benar memahami konsep RSC
    • Vercel menjadikan Next.js sebagai framework React default, dan adopsi RSC ikut meluas lewat arus itu
    • Bahkan orang yang memakai Next.js pun sering tidak memahami batas Server Component dengan jelas, sehingga adopsinya terasa seperti semacam "cargo cult"
    • Fakta bahwa React tidak menerima PR terkait Vite juga terasa mencurigakan. Dorongan terhadap RSC mungkin pada praktiknya bukan demi user atau developer, melainkan strategi perusahaan platform untuk menjual hosting platform mereka
    • Jika ditarik ke belakang, perekrutan besar-besaran tim asli React oleh Vercel juga terlihat seperti upaya untuk memimpin masa depan React
    • Ada tanggapan bahwa penilaian atas motivasi dan latar belakang historis itu keliru, sambil menjelaskan status dukungan Vite saat ini
    • Integrasi Vite disebut sedang diperbaiki oleh tim Vite karena ada kendala teknis bahwa lingkungan DEV membutuhkan bundling
    • Argumen bahwa orang tidak memahami RSC dianggap sebagai klaim yang berputar secara logis
    • Orang boleh saja tidak menyukai RSC, tetapi di dalamnya tetap ada banyak ide menarik yang layak dipinjam untuk teknologi lain
    • Bukan soal meyakinkan semua orang, tetapi berharap masing-masing bisa mengambil bagian yang aneh namun berguna
  • Tentu saja, SPA masih bisa dibuat menjadi situs statis dan diunggah ke CDN, dan Next.js juga bisa self-hosting dalam mode "dynamic"
    • Hanya saja, secara realistis sulit mengimplementasikan seluruh kemampuan serverless rendering Next.js di luar Vercel karena adanya "magic" yang tidak terdokumentasi
    • Masalah ini juga sudah ditanggapi lewat usulan resmi untuk memperkenalkan adapter agar API tetap konsisten di berbagai platform: https://github.com/vercel/next.js/discussions/77740
    • Saya cenderung melihat dorongan terhadap RSC bukan karena kepentingan bisnis perusahaan, melainkan karena kesadaran bahwa pola build website lama (SSR + sedikit progressive enhancement di klien) memang punya banyak kelebihan nyata
    • Bahkan hanya dengan SSR saja, sudah ada masalah ketika terlalu banyak business logic berpindah ke klien tanpa perlu
  • RSC sendiri teknologi yang menarik, tetapi menurut saya kurang masuk akal untuk penggunaan nyata
    • Ada beban besar untuk mempertahankan backend server Node/Bun dalam skala besar hanya demi merender komponen yang kompleks
    • Kombinasi halaman statis atau React SPA + server API Go jauh lebih efisien
    • Hasil yang mirip bisa dibuat dengan resource yang jauh lebih kecil
  • Fakta bahwa penjelasan teknologi baru itu rumit tidak otomatis berarti teknologinya tidak perlu atau abstraksinya salah; ada masalah yang memang layak menanggung kompleksitas
    • Saya ingin melihat bagaimana teknologi ini berevolusi ke depan
  • Ada pemikiran bahwa struktur kode RSC juga bisa dipakai untuk membangun halaman statis yang memecah HTML/CSS/JS menjadi potongan-potongan kecil
    • Placeholder '$1' yang diusulkan dalam tulisan itu bahkan bisa diganti dengan URI, jadi mungkin tidak butuh server sama sekali; kebanyakan kasus tidak benar-benar memerlukan SSR dinamis
    • Kekurangannya, untuk pendekatan seperti ini, kecepatan pipeline update menjadi penting saat konten berubah, terutama untuk deployment streaming situs statis terkompilasi ke S3
    • Misalnya pada situs berita dengan banyak artikel yang sudah diprerender, diperlukan pemrosesan diff konten yang cerdas agar saat hanya sebagian isi berubah, hanya bagian itu yang dibangun ulang secara efisien
  • Di pekerjaan nyata, orang bicara optimasi performa sambil memuat data beberapa MB di frontend dalam hitungan milidetik dan menjalankan logika kompleks, padahal solusi yang jauh lebih produktif sering kali adalah BFF, perbaikan arsitektur, atau membangun API yang lebih lean
    • Sudah ada percobaan lewat GraphQL, http2, dan lain-lain, tetapi pada akhirnya itu bukan penyelesaian masalah yang mendasar; tanpa evolusi standar web, tidak akan ada perubahan paradigma
    • Framework baru pun tetap menghadapi batas yang sama
  • RSC pada dasarnya memang berperan sebagai BFF (Backend for Frontend), seperti dijelaskan di akhir tulisan
  • Ada pendapat bahwa makna "mengurangi ms waktu loading halaman" bergantung pada metrik yang dimaksud
    • Jika yang dioptimalkan adalah time to first render dan time to visually complete, maka mengirim skeleton UI kosong lebih dulu lalu mengambil data via API dan meng-hydrate-nya terasa paling cepat
    • Sebaliknya, jika ingin mempercepat time to first input dan time to interactive, data user harus bisa langsung dirender, dan dalam kasus itu backend jauh lebih unggul karena meminimalkan network call
    • Dalam banyak kasus user lebih menyukai pendekatan kedua; untuk aplikasi CRUD SaaS, rendering sisi server cocok, sedangkan untuk aplikasi yang menekankan desain seperti Figma, data statis di klien + fetch data tambahan lebih sesuai
    • Tidak ada "satu solusi untuk semua masalah", dan titik optimasinya adalah pilihan yang subjektif
    • Pengalaman developer, struktur tim, dan berbagai faktor lain juga memengaruhi pilihan teknologi
  • Ini membantu saya memahami kenapa saat Facebook dimuat, konten utamanya selalu dirender paling akhir
  • Muncul pertanyaan tentang apa yang dimaksud dengan BFF di sini
  • Ada juga reaksi yang bingung karena terlalu banyak singkatan, dan bertanya FE serta BFF itu apa
  • Saya tidak ingin langsung memakai ide Progressive JSON, dan menurut saya ada beberapa alternatif
    • Solusi paling sederhana adalah memecah satu objek JSON raksasa menjadi beberapa bagian, yaitu mengirimnya sebagai 'JSON lines'
    • Informasi header dikirim sekali, lalu array besar dikirim per baris agar pemrosesan stream lebih efisien
    • Jika objeknya lebih besar lagi, pendekatan ini bisa diterapkan secara rekursif, meski mungkin jadi terlalu rumit
    • Server juga bisa secara eksplisit menjamin urutan properti agar progressive parsing dan pemisahan data memungkinkan
    • Pada akhirnya ini mungkin tidak terlalu berguna untuk struktur yang benar-benar sangat besar, tetapi sebagai alat praktis untuk kasus JSON besar yang paling umum, cukup masuk akal
  • Bahkan tanpa menandai lubang (holes) secara eksplisit, pesan streaming bisa dikirim berurutan sambil hanya mengirim delta (diff)
    • Dengan format delta bernama 'Mendoza', patch (diffs) bisa dikirim dengan sangat ringkas di Go dan JS/Typescript: https://github.com/sanity-io/mendoza, https://github.com/sanity-io/mendoza-js
    • Seperti pendekatan binary diff pada zstd atau Mendoza, hanya sebagian data serialisasi disimpan di memori agar patch bisa dilakukan secara efisien
    • React juga membutuhkan pendekatan yang membandingkan perbedaan atau hanya menyuntikkan perubahan, jadi ini terasa relevan
  • Dalam streaming data UI, array kosong atau null saja tidak cukup; perlu informasi terpisah tentang data mana yang masih belum datang (pending)
    • Payload streaming GraphQL memilih pendekatan campuran: skema data yang valid, informasi tentang bagian yang belum datang, lalu patch lanjutan sesudahnya
  • Agar status loading mudah ditampilkan, harus diketahui bagian mana yang merupakan "lubang"
  • Untuk progressive decode JSON di klien, ada rekomendasi library jsonriver: https://github.com/rictic/jsonriver
    • API-nya sangat sederhana, performanya bagus, dan pengujiannya juga memadai
    • Ia mem-parsing potongan string hasil streaming menjadi nilai yang makin lama makin lengkap
    • Hasil akhirnya dijamin sama dengan JSON.parse
  • Jika datanya berbentuk tree, ini terasa seperti pendekatan menarik yang bisa diterapkan pada struktur apa pun
    • Data tree bisa direpresentasikan dengan vektor parent, type, data, dan string table, sehingga sisanya dapat diperkecil menjadi sejumlah kecil integer
    • String table dan informasi type bisa dikirim lebih dulu sebagai header, lalu chunk vektor parent dan data di-stream per node
    • Streaming depth-first atau breadth-first cukup diatur lewat urutan chunk
    • Rasanya ini bisa sangat meningkatkan UX waktu muat pada aplikasi di atas jaringan
    • Dengan mengirim tabel dan chunk node secara bergantian, tree bisa divisualisasikan di web dalam urutan apa pun
    • Dengan preorder traversal dan informasi depth saja, struktur tree bahkan bisa direkonstruksi tanpa node id
    • Layak juga membuat library kecil dari ide ini
  • Ada pendapat bahwa sebagian besar aplikasi tidak membutuhkan sistem loading yang "secanggih" ini, dan biasanya cukup diganti dengan beberapa panggilan API biasa
    • Balasannya, tujuan awalnya hanya menjelaskan cara kerja wire protocol RSC, bukan benar-benar menganjurkan orang mengimplementasikan ini sendiri
    • Memahami prinsip berbagai tool pada akhirnya membantu mengambil atau me-remix ide di banyak tempat berbeda
    • Strategi multi-call memang dianggap punya masalah n*n+1, tetapi alih-alih mengirim objek bersarang ala OOP/ORM, data bisa juga dikirim secara datar seperti contoh komentar
    • Dalam situasi seperti ini, endpoint yang tipenya jelas dengan protobuf dan sejenisnya juga punya kelebihan
    • Jika comments dipisah, dua panggilan saja sudah cukup (halaman+post, komentar terpisah), dan ini juga memungkinkan optimasi pre-render
    • Selama ada tool bagus yang sudah disiapkan, tidak selalu perlu melakukan kustomisasi dalam yang berlebihan sambil menaikkan kompleksitas implementasi
    • Harus disadari bahwa fitur yang terlalu kompleks pada akhirnya bisa merugikan user maupun developer
    • Seperti ungkapan bahwa 640K sudah cukup, saya justru merasa progressive/partial reads bisa benar-benar berperan besar dalam kecepatan UX di era WASM
    • Jika encoding biner seperti protobuf digabung dengan partial read dan streaming yang terdefinisi jelas, beban engineer memang bertambah, tetapi UX hasilnya bisa meningkat besar
  • Menurut sebagian orang, Progressive JPEG itu perlu karena sifat file media, tetapi pada Text/HTML tidak terlalu perlu, dan situasi ini justru jadi kontradiktif karena kompleksitasnya lahir dari bundle JS yang membesar
    • Ini menyoroti bahwa penyebab lambat bukan semata karena "ukuran" data
    • Kadang query data server memang memakan waktu lama, atau jaringan lambat, dan dalam kondisi seperti itu progressive reveal tetap bermakna
    • Alih-alih menunggu semua data lengkap, rendering bertahap yang sengaja menampilkan loading UI pada timing yang tepat benar-benar bisa menguntungkan pengalaman pengguna
  • Strategi pemisahan endpoint dianggap sudah merupakan solusi yang memiliki banyak kelebihan, seperti mencegah head-of-line blocking, meningkatkan opsi filter, mendukung live update, dan memungkinkan perbaikan performa independen
    • Ada pandangan bahwa akar masalahnya adalah upaya memperlakukan aplikasi sebagai document platform
    • Aplikasi nyata tidak bekerja seperti "dokumen", dan untuk menjembatani kesenjangan itu akhirnya dibutuhkan banyak kode tambahan serta infrastruktur
    • Penjelasan tambahan tentang kelemahan nyata pendekatan endpoint terpisah dan arah evolusinya dijabarkan dalam dua tulisan panjang ini: https://overreacted.io/one-roundtrip-per-navigation/, https://overreacted.io/jsx-over-the-wire/
    • Ringkasnya, endpoint pada akhirnya menjadi kontrak API "resmi" antara server dan klien, dan seiring kode makin termodularisasi, performa bisa dirugikan, misalnya karena waterfall
    • Menggabungkan keputusan di server sekaligus (coalescing) bisa menjadi alternatif yang lebih baik dari sisi performa maupun fleksibilitas struktur