RSC Payload'ı Neden Şişiyor
Client'a bütün satırı geçirmek flight'ı şişirir

Network'te sayfa belgesi 80 KB iken hemen altında bir flight metni 900 KB geldi. Tıklanınca değil, ilk yüklemede. RSC payload, sunucunun client yapraklarına yazdığı serileşmiş ağaç. Ben o ağaca yirmi kartlık Prisma satırı gömmüştüm: HTML gövde, yazarın uzatılmış biyografisi, iç JSON, null alanlar. Kart üç satır gösteriyordu.
Flight, HTML değil. Client bileşenin props'u, sunucu çocukların referansı, slot'lar. Props büyükse uçuş büyük. HTML büyük olsa bile tarayıcı onu metin gibi boyar; flight ise hidrasyon ve istemci geçişinin ham maddesi. İkisini aynı 'sayfa ağır' cümlesinde toplamak, yanlış yeri kesmeni sağlar.
Select, DTO, yaprak
import { ArticleCardMenu } from "./article-card-menu";
import { db } from "@/lib/db";
export async function ArticleRail() {
const rows = await db.article.findMany({
where: { status: "published" },
orderBy: { publishedAt: "desc" },
take: 20,
select: {
id: true,
title: true,
slug: true,
excerpt: true,
publishedAt: true,
},
});
return (
{rows.map((row) => (
-
{row.title}
{row.excerpt}
))}
);
}
"use client";
import { useState } from "react";
import { toggleSave } from "./actions";
export function ArticleCardMenu({ articleId }: { articleId: string }) {
const [saved, setSaved] = useState(false);
return (
);
}
Rail sunucu. Menü yalnızca articleId alıyor. excerpt zaten sunucuda metin; client'a uçmuyor çünkü menü onu props olarak görmüyor. Eski hali ArticleCard'ı client yapıp row'un tamamını vermekti. Gövde HTML'i yirmi kez JSON'a yazılıyordu. Kullanıcı kaydet'e basmadan o HTML'e client'ta ihtiyaç yoktu.
Tarih, class, fonksiyon
Date düz ISO'ya inebilir; kendi Date sarmalayıcın inemez. class instance, prototype kaybeder veya hata verir. onClick'i sunucudan geçirmeye çalışmak zaten yasak. Görünür semptom bazen 'sadece düz nesne' hatası, bazen sessizce şişen bir toJSON. Prisma nesnesini olduğu gibi vermek, getter ve ilişki proxy'sini de taşımaya çalışmak demek. select + düz literali tercih ediyorum.
Import yönü
Client dosyasına sunucu-only bir modül import etmek, o modülün paketini client grafiğine çeker veya derlemeyi kırar. server-only paketi bunu bağırır. Tersi daha sinsi: sunucu dosyası, içinde 'use client' olan ağır bir chart kütüphanesini 'bir gün kullanırız' diye import eder. O chart yaprak olsa bile, yanlış yerden import edersen sınır bozulmaz; grafiği bilinçli yaprakta tutarsın. Ağır client'ı sunucu listesinin tepesine yazmak, her kart için chart runtime'ı ister.
children yine kurtarıcı. Büyük gövdeyi client moda children ver, props olarak verme. Modal kabuğu client, makale sunucu çocuk. Uçuşta gövde HTML çocuk olarak referanslanır, JSON string olarak kopyalanmaz. Birinci yazıdaki çekmece ile aynı desen; kiloyu orada da, burada da children taşır, props taşımaz.
Nereye bakıyorum
Chrome'da document'ten sonra gelen text/x-component veya flight parçası. Satır sayısı, tekrar eden HTML. Bir excerpt yirmi kez aynı uzunlukta duruyorsa sorun tekrar; bir body yirmi kez duruyorsa sorun select. Kesince TTFB düşmeyebilir — sorgu zaten hızlıdır — ama hidrasyon ve istemci geçişi incelir. Payload şişmesi yavaş sorgu değil, fazla konuşkan bir sınır.
Kartın kullandığı alanları sayıyorum. Üçse üç prop. 'Yarın beğeni sayısı da lazım' yarının PR'ı. Bugünün uçuşu, bugünün yaprağı. Analyzer'a ek olarak flight'ı bir kez okumak, Server Component yazmayı ciddiye almak için yeterli.
Gövdeyi props diye vermek
Markdown'dan üretilmiş HTML'i client önizleme penceresine string olarak vermek, tek prop'ta 40 KB demek olabilir. Önizleme gerçekten client'ta canlı yazıyorsa bu bedel bilinçli. Kart listesinde aynı HTML'i 'belki tooltip' diye vermek bilinçsiz. Tooltip sunucuda da durur. Client'a string geçirmeden önce o string'in tıklanınca mı, boyanınca mı lazım olduğunu soruyorum.
Görseli base64 data URL olarak prop yapmak, flight'ı görsel kadar şişirir. src bir yol olsun, Image veya img o yolu çözsün. Avatar'ı satıra gömmek, yirmi kartta yirmi kopya. Public path veya CDN URL, tek satır. Payload, medyayı taşımak için değil, etkileşimi bağlamak için var.
Listeyi client'a devretmek
Süzgeç yüzünden tüm ızgarayı client yapıp products dizisini olduğu gibi vermek, yedinci yazıdaki kas ile on ikinci yazıdaki kiloyu birleştirir. Süzgeç client, dizi sunucuda HTML olur, children veya düz liste durur. Client yalnızca seçili tag'i bilir, dizi onun props'u değildir. Dizi props ise uçuşta yirmi başlık, yirmi özet, yirmi tarih bir daha yazılır. HTML zaten yazmıştır; bir daha yazmaya gerek yok.
export async function FilteredRail({ tag }: { tag: string }) {
const rows = await listPublished(tag);
return (
{rows.map((row) => (
-
{row.title}
))}
);
}
FilterShell client, children sunucu liste. Tag değişince sayfayı searchParam ile yenilemek, diziyi client'ta tutmaktan daha ucuz olabilir. Yenileme ağırsa o zaman dizi props olur; bedeli görerek alırım. Varsayılanım children. Varsayılanım 'bütün satırı ver' değil.
Gizli şişmanlar
Prisma'nın Decimal, Date, enum değerleri düz JSON'a inerken string veya sayı olur. Özel bir para sınıfı inemez. toJSON yazmadan satırı vermek, ya hata ya şişme. Hata iyidir, görürsün. Şişme, null ilişkilerin boş nesne olarak durmasıdır: author: { id, name, bio, links, addresses }. Kart name ister. select author.name, gerisini bırak.
Aynı şekilde 'include: { tags: true, author: true, revisions: true }' bir detay sayfasında bile fazla olabilir. Revisions client'a hiç gitmemeli. Detay sunucudaysa include orada kalır, uçuşa yazılmaz. Detayın bir köşesi client ise o köşeye revision listesini değil, revisionCount ver. Sayı dört karakter. Liste dört kilobayt.
Ölçüm
DevTools'ta dokümanı açıp type'ı text/x-component veya model olan istekleri sıralıyorum. Boyut, kart sayısıyla lineer büyüyorsa props sızıyor. HTML belgesi de büyür ama o boyama; flight hidrasyon. İkisini ayırınca 'sayfa ağır' cümlesi ikiye bölünür. CSS ve font ayrı. Flight ayrı. Kestiğim yer select ve client sınırı. Analyzer JS'i gösterir, flight'ı her zaman göstermez. Network, gösterir.
Birinci yazı sınırı anlattı, bu yazı sınırdan geçen kiloyu. İkisini birlikte tutunca Server Component bir slogan olmaktan çıkar. Slogan, 'sunucuda render'. İş, 'tarayıcıya neyin yazılacağını seçmek'. Seçmeyince çerçeve senin yerine her prop'u yazar. Yazar, çünkü sen verdin.
Paralel slot da yazar
On üçüncü yazıdaki modal slot, ikinci bir ağaç. O ağaca da büyük props giderse flight ikiye katlanır. Overlay yalnızca id, src, alt alsın. Galeri listesi children'da kalsın. Slot'u 'madem var, her şeyi oraya' diye doldurmak, payload'ı ikinci kapıdan şişirmek. Kapı çoğalınca kilo da çoğalır. Her kapıda select.
Draft mode ve önizleme de ayrı bir uçuş üretebilir. Önizlemede tam satırı göstermek bilinçli olabilir. Yayın rotasında aynı bileşeni paylaşmak, draft alanlarını da uçuşa sokar. isDraft kontrolü sunucuda kalsın, client'a yalnızca yayın DTO'su gitsin. 'Aynı kart her yerde' cümlesi, aynı kiloyu her yere taşır. Kart aynı görünsün, props aynı olmak zorunda değil.
Çeviri sözlükleri
Tüm i18n sözlüğünü client yaprağına vermek, her sayfada megabayt olmasa da yüz kilobayt ekler. Yaprağın kullandığı üç cümleyi ver. Sözlük sunucuda kalır, formatlanmış metin HTML olur. 't fonksiyonunu client'a geçiremem' diye sözlüğü geçirmek, fonksiyon yasağının yanlış çözümü. Çözüm, metni sunucuda üretmek veya küçük bir sözlük parçası.
Tema token'ları, feature flag haritası, kullanıcı izin matrisi — üçü de 'belki lazım' diye client'a yığılır. Flag bir boolean, matris bir dizi, token bir sınıf adı. Haritanın tamamı değil. Yetki matrisini client'a vermek ayrıca bir güvenlik notu: UI gizlemek, yetki değildir. Payload hem şişer hem sızdırır. On ikinci yazı kiloyu keser; sızıntı ayrı utanç.
Ne kadar ince yeter
Yirmi kartlık bir ray için flight'ın belgeden küçük durmasını istiyorum. Belge 40 KB, flight 900 KB ise oran bozuk. Belge 40, flight 15 ise konuşmuyorum. Mutlak sayı sayfaya göre değişir; oran, şişmeyi bağırır. Kart başına birkaç yüz byte prop kabul; kart başına birkaç kilobayt gövde kabul değil.
Kestikten sonra bir kez daha analyzer'a bakıyorum. Bazen kilo JS'deymiş, flight değil. O zaman ikinci yazı. Bazen sorgu yavaş, flight ince. O zaman cache veya indeks. Flight'ı her yavaşlığın suçlusu ilan etmiyorum. Network'te ayrı satır, ayrı teşhis. Teşhis durunca kesilecek yer durur. Yer, neredeyse her zaman fazla prop.
Map ve Set
Client'a Map geçirmek serileşmez. Object.entries ile düz diziye indiriyorum. Set de aynı. 'Sunucuda Map rahat' doğru; yaprağa giden şey rahat olmak zorunda değil, düz olmak zorunda. Düzeltmeyi yaprakta değil, sınırda yapıyorum. Yaprak düz dizi bekler, Map görmez. Görmezse flight da proto taşımaz.
Büyük tabloyu client sort için vermek, on ikinci yazının klasik tuzağı. Sort sunucuda veya searchParam ile. Client sort istiyorsan sütunları kes, satırları kes, sayfala. 500 satır × 20 kolon JSON, HTML'den ağır durur. Tablo sunucu, sıralama linkleri sunucu, süs client. Birinci yazıdaki cümle burada kilo olarak görünür.
Son kesim
PR şablonuma bir kutu ekledim: 'bu client bileşen kaç prop, en uzun string hangisi'. Kutu boşsa bakıyorum. Doluysa uçuşu tahmin edebiliyorum. Tahmin, analyzer kadar kesin değil; fazla prop'u durdurmaya yeter. On ikinci yazı bir manifesto değil. Bir kutu. Kutuyu doldurunca payload inceliyor.
Çift yazım
Aynı excerpt hem HTML'de hem props'ta duruyorsa flight gereksiz kopyadır. Client excerpt'i kullanmıyorsa prop'u sil. Kullanıyorsa HTML'den çıkar, yalnızca yaprak boyasın — o zaman da ilk boyada metin gecikir. Tercihim HTML'de bırakmak, prop'u silmek. Çift yazım, on ikinci yazının en sık görülen sessiz şişmanı. Network'te aynı cümleyi iki kez okuyorsan kopya var demektir.
Bunu bir kez kartta görünce tüm client yapraklarda arıyorum. title, date, href çoğu zaman sunucuda olur. Yaprak yalnızca id ister. İstemezse uçuş incelir. İnce uçuş, birinci yazının sınırının görünür hali. Sınır doğru, kilo yanlış olabilir. İkisini ayrı ölç. İkisini ayrı kes.
Yorumlar
Yorumlar (0)
Yorumlar üyelere açık. Üye ol · Giriş yap