15 puan yazan GN⁺ 2025-06-02 | 1 yorum | WhatsApp'ta paylaş
  • Aşamalı JPEG gibi, JSON verilerinin de önce eksik halde gönderilip istemcinin zaman içinde içeriğin tamamını giderek daha fazla kullanabilmesini sağlayan bir yaklaşım tanıtılıyor
  • Mevcut JSON ayrıştırma yöntemi, tüm veri tamamen alınmadan hiçbir işlem yapılamaması gibi verimsizlik sorunlarına sahip
  • Veriler breadth-first yaklaşımıyla birden çok parçaya ayrılıyor; henüz hazır olmayan kısımlar Promise olarak işaretleniyor ve hazır oldukça aşamalı biçimde dolduruluyor, böylece istemci tamamlanmamış verileri de kullanabiliyor
  • Bu kavram, React Server Components(RSC) için temel yeniliklerden biri ve <Suspense> ile kasıtlı, aşamalı yükleme durumları kontrol ediliyor
  • Veri akışını ve kasıtlı UI yükleme akışını birbirinden ayırarak daha esnek bir kullanıcı deneyimi sunmak mümkün

Aşamalı (Progressive) JPEG ve Progressive JSON fikri

  • Aşamalı JPEG, görseli yukarıdan aşağıya tek seferde yüklemek yerine önce bulanık halde tamamını gösterip zamanla netleştirir
  • Benzer şekilde, JSON aktarımına da aşamalı yaklaşım uygulanarak tamamı hazır olana kadar beklemeden verinin bir kısmını anında kullanmak mümkün olur
  • Örnek JSON veri yapısında, normal yöntemde ayrıştırma ancak son bayt da geldikten sonra yapılabilir
  • Bu yüzden istemci, sunucunun yavaş kısımları da dahil olmak üzere (ör. yavaş bir DB'den comments yüklenmesi) her şey tamamen aktarılana kadar beklemek zorunda kalır; bu da güncel standarttaki ciddi verimsizliklerden biridir

Streaming JSON ayrıştırıcısının sınırları

  • Bir streaming JSON ayrıştırıcısı kullanılırsa eksik (ara) veri nesnesi ağacı oluşturmak mümkündür
  • Ancak her nesnenin alanları (ör. footer, birden fazla comment listesi vb.) yalnızca kısmen iletildiğinde tip uyumsuzluğu oluşur, tamamlanma durumunu anlamak zorlaşır ve kullanılabilirlik düşer
  • HTML'in streaming render yaklaşımına benzer biçimde, akışın sırayla işlenmesi tek bir yavaş parçanın tüm sonucu geciktirmesi sorununu yine üretir
  • Streaming JSON'un genelde yaygın kullanılmamasının nedenlerinden biri de budur

Progressive JSON yapısı önerisi

  • Klasik derinlik öncelikli streaming yerine, breadth-first (genişlik öncelikli) yaklaşım öneriliyor
  • Önce yalnızca en üst düzey nesne gönderiliyor; alt değerler ise Promise benzeri placeholder'lar olarak bırakılıyor ve hazır oldukça ayrı parçalar halinde dolduruluyor
  • Örneğin sunucu, asenkron veri yüklemeleri tamamlandıkça karşılık gelen parçaları gönderiyor; istemci de yalnızca hazır olan kısmı kullanabiliyor
  • Böylece asenkron veri alımı (erken yükleme) mümkün oluyor ve tüm yavaş parçaların bitmesini beklemek gerekmiyor
  • İstemciyi parça bazında sırasız ve kısmen sıralı alıma dayanıklı kurarsanız, sunucu da farklı parça bölme stratejilerini esnek biçimde uygulayabilir

Inlining ve Outlining: verimli veri aktarımı

  • Aşamalı JSON streaming formatı, yeniden kullanılan nesneleri (ör. aynı userInfo'nun birden çok yerde referanslanması) tekrar saklamadan tek bir parçaya ayırıp her konumda aynı referansı kullanmaya da izin verir
  • Yalnızca yavaş kısımlar placeholder olarak ayrılırken, geri kalan kısımlar hemen doldurularak verimli bir veri akışı sağlanır
  • Aynı nesne birden çok kez görünüyorsa, yalnızca bir kez gönderilip yeniden kullanılabilir (Outlining)
  • Bu sayede döngüsel referanslar (bir nesnenin kendisini referanslaması) da normal JSON'daki gibi sorun yaratmadan parçalar arası dolaylı referanslarla doğal biçimde serileştirilebilir

React Server Components(RSC) içinde aşamalı streaming uygulaması

  • Gerçek dünyada React Server Components, aşamalı JSON streaming modelinin en tipik örneklerinden biridir
    • Sunucu, dış verileri (ör. Post, Comments) asenkron olarak yükleyen bir yapı kullanır
    • İstemci tarafında henüz ulaşmamış bölümler Promise olarak tutulur ve hazır olma sırasına göre UI aşamalı biçimde render edilir
  • React'in <Suspense> özelliğiyle kasıtlı yükleme durumları tanımlanır
    • Kullanıcı deneyiminde gereksiz ekran sıçramalarını önlemek için Promise durumları (boşluklar) doğrudan gösterilmez; bunun yerine <Suspense> fallback ile kademeli yükleme deneyimi tasarlanabilir
    • Veri çok hızlı gelse bile geliştirici, gerçek UI'ın tasarlanan aşamalara göre kademeli görünmesini kontrol edebilir

Özet ve çıkarımlar

  • React Server Components'in temel yeniliği, bileşen ağacının props'larını dıştan içe doğru aşamalı olarak stream etmesinde yatıyor
  • Bu sayede sunucunun tüm veriyi tamamen hazırlamasını beklemeden, önce önemli kısımlar gösterilebilir ve yükleme bekleme durumu daha ince biçimde kontrol edilebilir
  • Yalnızca streaming değil, bunu kullanan programlama modelinin de (React'in <Suspense> yapısı gibi) birlikte bulunması gerekir
  • Böylece, tek bir yavaş veri parçasının tüm aktarımı geciktirmesi gibi mevcut yöntemlerin darboğazları hafifletilebilir

1 yorum

 
GN⁺ 2025-06-02
Hacker News görüşleri
  • Bazı insanların bu yazıyı fazla kelimesi kelimesine yorumlama eğiliminde olduğu görülüyor; Dan Abramov'un Progressive JSON diye yeni bir format önermediği açıklanıyor
    • Bu yazı, React Server Components fikrini ve bileşen ağacını JavaScript nesneleri biçiminde ifade edip bunu akış olarak iletme sürecini anlatıyor
    • Bu yöntem, React bileşen ağacında “boşluklar” bırakmayı mümkün kılıyor; böylece önce loading durumu ya da skeleton UI gösterilip, sunucudan gerçek veri gelince ilgili bölüm tamamen render edilebiliyor
    • Böylece daha ince taneli loading göstergeleri ve daha hızlı ilk ekran gösterimi mümkün oluyor
  • Bence insanların bu fikri genişletip başka yöntemlere uygulaması da gayet makul
    • Amaç, RSC verisini serileştirme yöntemini yalnızca React ile sınırlamak yerine daha genel bir kalıp olarak açıklamaktı
    • React Server Components içinde bulunan çeşitli fikirlerin başka teknolojilere ya da ekosistemlere de yayılması umuluyor
  • Ben progressive loading yaklaşımını, özellikle de içeriğin sürekli hareket ettiği (zıpladığı) deneyimi pek sevmiyorum
    • Yükleme sırasında boş durum UI'ı gösterme kalıbı özellikle rahatsız edici geliyor
  • Kısa süre öncesine kadar Ember kullanırken de benzer bir yöntem vardı ve Ajax endpoint yazmanın çok acı verici olduğunu hatırlıyorum
    • Ağaç yapısını yeniden düzenleyip bazı çocuk öğeleri dosyanın sonuna yerleştirerek DAG (döngüsüz grafik) işlemeyi verimli hale getirme niyeti var gibiydi
    • SAX tarzı bir streaming parser kullanılırsa veri parça parça gelirken boyamaya daha erken başlanabiliyor
    • Ama tek iş parçacıklı bir VM'de iş sırası kötü tasarlanırsa sorunlar daha da büyüyebilir
  • Ben zaten AI araçlarıyla bağlantıda streaming partial JSON (Progressive JSON) yaklaşımını fiilen kullanıyorum
    • Bunun yalnızca RSC için değil, pek çok yerde kullanılabildiğini ve pratikte hem istemci hem sunucu tarafında değerli bir yöntem olduğunu bizzat deneyimledim
  • Dan'in "2 computers" sunumunu ve son RSC yazılarını takip ettim
    • Dan, React ekosistemindeki en iyi anlatıcılardan biri ama bir teknolojiyi bu kadar zor anlatmak gerekiyorsa
      1. ya gerçekten gerekli olmayan bir teknolojidir
      2. ya da soyutlamada sorun vardır
    • Ön uç geliştiricilerinin büyük kısmı hâlâ RSC kavramını tam olarak anlamış değil
    • Vercel, Next.js'i fiili React framework'ü haline getirdi ve RSC benimsenmesi de bu rüzgârla yayıldı
    • Next.js kullananlar bile Server Component sınırlarını net biçimde anlamıyor; bir tür “cargo cult” tarzı benimseme çok yaygın
    • React'in Vite ile ilgili PR'ları kabul etmemesi de şüphe uyandırıyor. RSC itişinin gerçekte kullanıcılar ya da geliştiriciler için değil, platform şirketlerinin hosting platformu satma stratejisi olabileceği düşünülüyor
    • Geriye dönüp bakınca, Vercel'in React'in orijinal ekibinden çok sayıda kişiyi işe alması da React'in geleceğini yönlendirme niyeti gibi görünüyor
    • Tarihsel motivasyon ya da arka plan değerlendirmesinin yanlış olduğu belirtilerek, Vite desteğinin güncel durumu açıklanıyor
    • Vite entegrasyonunda, DEV ortamında bundling gerekliliği gibi teknik bir kısıt var ve şu anda Vite ekibi bunun üzerinde çalışıyor deniyor
    • İnsanların RSC'yi anlamadığı iddiasının mantıksal olarak döngüsel bir argüman olduğu görüşü de var
    • RSC sevilmeyebilir ama içinde başka teknolojilere uyarlanabilecek yeterince ilginç fikir bulunuyor
    • Amaç ikna etmekten çok, insanların ilginç ve yararlı buldukları parçaları alıp kullanması
  • Elbette hâlâ bir SPA'yı statik siteye çevirip CDN'e koymak mümkün ve Next.js de “dynamic” modda self-host edilebiliyor
    • Yine de Next.js'in serverless rendering yeteneklerinin tamamını Vercel dışında başka yerde eksiksiz uygulamak zor; nedeni undocumented “magic”
    • Bu sorun için de çoklu platformlarda tutarlı API sağlamak amacıyla adapter eklenmesi resmen önerilmiş durumda: https://github.com/vercel/next.js/discussions/77740
    • Bana göre RSC itişi şirket çıkarından çok, eski web sitesi geliştirme kalıbının (SSR + istemcide biraz progressive enhancement) gerçekte pek çok avantaja sahip olduğunun fark edilmesinden doğdu
    • Sadece SSR kullanıldığında bile iş mantığının gereksiz yere istemciye taşınması sorunu var
  • RSC kendi başına ilginç bir teknoloji ama gerçek kullanımda çok da mantıklı görünmüyor
    • Karmaşık bileşenleri render etmek için büyük ölçekte Node/Bun backend sunucuları çalıştırma yükü var
    • Bunun yerine statik sayfalar ya da React SPA + Go API sunucusu kombinasyonu çok daha verimli olabilir
    • Benzer sonuçlar çok daha az kaynakla elde edilebilir
  • Yeni bir teknolojinin anlatımının karmaşık olması, onun gereksiz ya da yanlış soyutlama olduğu anlamına gelmez; bu karmaşıklığı karşılamaya değer problemler de vardır
    • Bu teknolojinin gelecekte nasıl evrileceğini görmek gerektiği düşünülüyor
  • RSC'nin kod yapısı kullanılarak HTML/CSS/JS'yi küçük parçalara ayıran statik sayfa derlemeleri de yapılabilir gibi görünüyor
    • Yazıda önerilen ‘$1’ placeholder'ı URI ile değiştirerek bile sunucu gerekmeyebilir; çoğu durumda dinamik SSR gerçekten şart değil
    • Dezavantajı, bu yöntemde içerik değiştiğinde güncelleme hattının, özellikle de S3'e derlenmiş statik sitenin streaming dağıtımında, yeterince hızlı olması gerekliliği
    • Örneğin çok sayıda makalesi önceden render edilmiş bir gazete sitesinde olduğu gibi, içeriğin sadece bir kısmı değiştiğinde sadece o bölümü verimli biçimde yeniden derlemek için akıllı content diff işleme gerekir
  • Gerçek hayatta performans optimizasyonu denilerek ön yüzde milisaniye düzeyinde kazanım için birkaç MB veri yüklenip karmaşık mantık çalıştırılırken, aslında BFF ya da mimari iyileştirme ve daha lean API kurmanın çok daha üretken çözümler olduğu fark ediliyor
    • GraphQL, http2 vb. üzerinden denemeler yapıldı ama bunlar nihai sorunu çözmedi; web standartları evrilmeden paradigma değişimi olmayacağı görüşü var
    • Yeni framework'ler de bu sınırdan kaçamıyor
  • RSC, yazının sonunda açıklandığı gibi, özünde bir BFF (Backend for Frontend) rolü görüyor
  • "Sayfa yükleme süresinden ms kısaltmak" ifadesinin neyi kastettiğine göre durum değişir
    • Time to first render ve time to visually complete optimize edilecekse, önce boş bir skeleton UI gönderip sonra API'den veri alarak hydrate etmek algısal olarak en hızlı yöntem gibi görünüyor
    • Buna karşılık time to first input ve time to interactive hızlandırılmak isteniyorsa, kullanıcı verisinin doğrudan render edilebilmesi gerekir; bu durumda backend çok daha avantajlıdır çünkü ağ çağrıları azalır
    • Çoğu durumda kullanıcılar bunu daha çok tercih eder; CRUD SaaS uygulamalarında server-side rendering, Figma gibi tasarımın kritik olduğu uygulamalarda ise istemcide statik veri + ek veri fetch yaklaşımı daha uygundur
    • “Her soruna uyan tek çözüm” yoktur ve optimizasyon noktası öznel bir tercihtir
    • Geliştirici deneyimi, ekip yapısı gibi teknoloji seçimini etkileyen birçok unsur vardır
  • Bu sayede Facebook yüklenirken neden ana içeriğin hep en son render edildiğini şimdi anladım
  • Burada sözü edilen BFF'nin ne olduğu soruluyor
  • Kısaltma çok fazla olduğu için FE ve BFF'nin ne olduğunu soran tepkiler var
  • Progressive JSON fikrini doğrudan kullanmak istemem; bence birkaç alternatif var
    • En basit çözüm, devasa tek bir JSON nesnesini birkaç parçaya ayırmak, yani ‘JSON lines’ olarak göndermek
    • Header bilgisi bir kez, büyük diziler ise satır satır gönderilerek stream işlemesi daha verimli hale getirilebilir
    • Nesne daha da büyükse bu yöntem özyineli olarak uygulanabilir ama fazla karmaşıklaşabilir
    • Sunucu özellik sırasını açıkça garanti ederek progressive parsing ve ayrıştırma da mümkün olabilir
    • Sonuçta gerçekten çok büyük yapılarda ideal olmayabilir ama büyük JSON ile ilgili en yaygın senaryolarda oldukça pratik bir araç olur
  • Açıkça “boşluklar” işaretlenmeden de streaming mesajlar sıralı gönderilip yalnızca delta (diff) iletilebilir
    • ‘Mendoza’ adlı delta formatı kullanılırsa Go ve JS/Typescript tarafında çok kompakt patch (diffs) aktarımı mümkün: https://github.com/sanity-io/mendoza, https://github.com/sanity-io/mendoza-js
    • zstd'nin binary diff yaklaşımında ya da Mendoza'da olduğu gibi, serileştirilmiş verinin sadece bir kısmını bellekte tutarak verimli patch uygulanabilir
    • React'in de farkları karşılaştırma ya da sadece değişiklikleri enjekte etme ihtiyacı olduğu için anlamlı bir yaklaşım
  • UI veri akışında yalnızca boş diziler ya da null yeterli değil; hangi verinin henüz gelmemiş (pending) durumda olduğunu gösteren ek bilgi gerekiyor
    • GraphQL streaming payload'ları, geçerli veri şeması + henüz gelmemiş bilgi + sonradan gelen patch işleme şeklinde karma bir yöntem seçiyor
  • Hangi kısmın “boşluk” olduğunu bilmek, loading durumunu göstermeyi kolaylaştırıyor
  • İstemci tarafında JSON'u progressive decode etmek için jsonriver adlı kütüphane öneriliyor: https://github.com/rictic/jsonriver
    • API'si çok basit, performansı iyi ve yeterince test edilmiş
    • Stream edilen string parçalarını giderek daha tamamlanmış değerlere parse ediyor
    • Sonucun nihayetinde JSON.parse ile aynı olmasını garanti ediyor
  • Ağaç verisiyse bu, her türlü yapıya uygulanabilecek eğlenceli bir yöntem gibi duruyor
    • Ağaç verisi, parent, type, data vektörleri ve bir string table ile ifade edilirse geri kalan her şey birkaç küçük tamsayıya indirgenebilir
    • String table ve type bilgisi başlıkta upfront gönderilip, parent ve data vektörlerinin chunk'ları düğüm bazında stream edilebilir
    • Depth-first ya da breadth-first streaming için yalnızca chunk sırasını değiştirmek yeterli olur
    • Ağ üzerinden çalışan uygulamalarda yükleme süresi UX'ini ciddi biçimde iyileştirebilir gibi görünüyor
    • Tablolar ve düğüm chunk'ları dönüşümlü gönderilerek ağaç web üzerinde istenen sırayla görselleştirilebilir
    • Preorder traversal ve derinlik bilgisi varsa node id olmadan bile ağaç yapısı yeniden kurulabilir
    • Bu fikirle küçük bir kütüphane yapmak değerli bir deneme olabilir
  • Çoğu uygulamanın böyle “sofistike” yükleme sistemlerine ihtiyacı yok; çoğu zaman basitçe birkaç API çağrısıyla çözülebilir deniyor
    • Buna karşılık, burada amaç yalnızca RSC wire protocol'ünün nasıl çalıştığını açıklamaktı; insanlara gidip bunu doğrudan uygulayın denmiyor
    • Farklı araçların çalışma mantığını anlamak, başka yerlerde fikir ödünç almaya ya da yeniden karıştırmaya yardımcı olur
    • Birden çok çağrı stratejisinin n*n+1 sorunu yaratabileceği düşünülüyor ama nesneleri OOP/ORM tarzı iç içe göndermek yerine, yorum örneğinde olduğu gibi düzleştirerek göndermek de mümkün
    • Bu noktada protobuf gibi tipleri net endpoint'ler kurmanın da avantajı olabilir
    • comments ayrılırsa 2 çağrıyla yetebilir (sayfa+yazı, yorumlar ayrı); böylece pre-render optimizasyonu da yapılabilir
    • Seçeneklerin gerçek uygulama karmaşıklığını gereğinden fazla büyütmeden, önceden tanımlı iyi araçlar varsa derin özelleştirmeye gitmeye gerek kalmaz
    • Aşırı karmaşık özelliklerin sonunda kullanıcıya ya da geliştiriciye zarar verebileceği unutulmamalı
    • 640K yeterlidir sözünde olduğu gibi, progressive/partial read'lerin özellikle WASM çağında UX hızında gerçekten büyük rol oynayabileceği düşünülüyor
    • protobuf gibi binary encoding yaklaşımlarına partial read ve iyi tanımlı streaming eklendiğinde mühendislik yükü artsa da sonuçtaki UX ciddi biçimde iyileşebilir
  • Progressive JPEG, medya dosyalarının doğası gereği gerekli olabilir ama Text/HTML için şart değil; büyüyen JS bundle'larının sonucu olarak karmaşıklığı artıran çelişkili bir durum olduğu görüşü var
    • Gerçek yavaşlığın nedeninin sadece verinin “boyutu” olmadığı vurgulanıyor
    • Sunucu veri sorgularının kendisi uzun sürebilir ya da ağ yavaş olabilir; bu durumda progressive reveal yine anlamlıdır
    • Tüm verinin tamamlanmasını beklemek yerine uygun anda loading UI gösterip aşamalı render yapmak, kullanıcı deneyimine gerçekten fayda sağlayabilir
  • Endpoint ayırma stratejisinin zaten birçok avantaj sunduğu düşünülüyor: head-of-line blocking'i önleme, daha iyi filtre seçenekleri, canlı güncellemeler, bağımsız performans iyileştirmeleri vb.
    • Uygulamayı bir document platform olarak ele alma çabasının temel sorun olduğu görüşü de var
    • Gerçek uygulamalar bir “belge” gibi çalışmadığı için bu farkı kapatmak adına çok sayıda ek kod ve altyapı gerekiyor
    • Ayrı endpoint benimsemenin gerçek dezavantajları ve evrim yönü şu iki uzun yazıda anlatılıyor: https://overreacted.io/one-roundtrip-per-navigation/, https://overreacted.io/jsx-over-the-wire/
    • Özetle endpoint'ler zamanla sunucu/istemci arasındaki “resmî” API sözleşmesine dönüşüyor ve kod modülerleştikçe performans kaybı (waterfall etkisi vb.) ortaya çıkabiliyor
    • Kararları sunucuda tek seferde birleştirmek (coalescing), hem performans hem yapısal esneklik açısından daha iyi bir alternatif olabilir