Bir veri grid'i hydrate olmadan önce ne çizer

Bir kurulum rehberi ssr false'u zorunlu bir adım yapıyorsa, bileşenin sunucuda neye dokunduğunu sormaya değer. Bir grid'in orada render olabilmesi için gerçekte ne olması gerektiği, ve hydration sonrası ne değiştiği burada.

KanuniLabs5 dk okuma

const Grid = dynamic(() => import('grid-package'), { ssr: false }), kendiniz seçtiğinizde makul bir satır. Kendi anasayfamız canlı demoları için bunu kullanıyor; demolar sayfanın ilk bundle'ını büyütmek yerine ayrı bir chunk olarak yükleniyor. Bir kurulum rehberi onu zorunlu bir adım yaptığında ise durum farklı, çünkü o zaman anlamı şu: bileşen sunucuda hiç çalışamıyor, bu yüzden sayfa bir yükleme placeholder'ı gönderiyor, JavaScript geldikten sonra gerçek grid'i yerine koyuyor, ve bundle'ınızı çalıştırmayan her crawler grid'in olması gereken yerde boş bir kutu görüyor.

"Bileşen çok fazla etkileşimli state tutuyor" bunu zorunlu kılmıyor. Zorunlu kılan genellikle bileşenin kodunun bir effect'in dışında dokunduğu belirli bir şey — window, document, ResizeObserver, ölçülmüş bir eleman boyutu — ve bunların hiçbiri Next sayfanızı bir string'e render ederken sunucuda yok, çünkü o sunucu hiçbir zaman bir tarayıcı sekmesi açmadı.

Sunucuda gerçekte ne kırılıyor

Bir grid üç tür iş yapar, ve bunlardan yalnızca biri gerçekten bir sunucu sorunu.

Veriyi ve sütunları okumak sıradan JavaScript'tir. Bir diziyi dolaşmak veya bir columns prop'unu okumak için tarayıcıya ihtiyaç yoktur.

Arayüzün şeklini çizmek — başlık, araç çubuğu, ARIA rolleri, sütun başlıkları — sıradan React'tir. Diğer her bileşen gibi bir string'e render olur.

Görüntü alanını ölçmek ve kaç satırın sığdığına karar vermek henüz mümkün değildir. Tarayıcı henüz hiçbir şeyi yerleştirmedi. useEffect içinde değil de render yolunda ResizeObserver çağıran veya element.clientHeight okuyan bir grid, Node'da hata fırlatır — ve ssr: false, o hatanın nedenini bulmadan en hızlı yok olma yoludur.

Grid'imiz her iki tarafta ne çiziyor

SunucudaHydration sonrası
Kök, başlık, ARIA rollerirender edilirdeğişmez
Sütun başlıklarırender edilirdeğişmez
Satır penceresiboşölçülür ve doldurulur
Web Workerhiç oluşturulmaz~20.000 satır eşiğinin üstünde oluşturulur
Uzak DataSourceload() bir kez çağrılır, hiç beklenmezistemci yükler

Satır penceresi boş, çünkü satır sayısı konteynerin yüksekliğine bağlı ve sunucunun ölçebileceği bir konteyneri yok. Tahmin edebilirdi — yaygın bir görüntü alanı yüksekliği diye, mesela, 20 satır render edebilirdi — ama bir tahmin çoğu ekran için yanlış olur, ve istemci gerçek konteyneri ölçtüğü an o satırları değiştirirdi. Sunucunun gerçekten gönderdiği şey iskelet ve sütun başlıkları, yani hydration öncesi sayfa boş bir div değil, okunabilir gerçek bir grid şekli.

O tablonun bir satırı iki kez okunmaya değer. Uzak bir kaynakta sunucu render'ı load()'unuzu bir kez çağırıyor ve onu beklemiyor; markup satırsız gidiyor, istemci de hydration sonrası kendi satırlarını yüklüyor. Mutlak bir URL ile bu, her sayfa render'ında cevabı atılan fazladan bir istek demek. Göreli bir fetch('/api/...') ise sunucuda sadece başarısız oluyor, ve grid bu hatayı render'ı bozmadan yutuyor.

Bu, güvenmenizi istediğimiz bir iddia değil — DOM'suz bir ortamda bileşeni bir string'e render eden testlerle kapsanıyor: biri araç çubuğu, filtre satırı, başlık filtresi, grup paneli, sayfalayıcı ve özet çubuğu aynı anda açıkken, ayrı bir tanesi de uzak bir veri kaynağının, sunucu onu beklemeden render edildiğini kontrol ediyor.

Next.js'te bu neye benziyor

Grid bir client component, çünkü state tutuyor ve olaylara abone oluyor — bu kısım gerçekten kaçınılmaz, bu veya herhangi bir etkileşimli bileşende. Kaçınılabilir olan şey, bu sınırı tüm sayfaya kadar yukarı itmek.

'use client';

import { DataGrid } from '@kanunilabs/datagrid-react';
import '@kanunilabs/datagrid-react/styles.css';

export function SalesGrid({ rows }) {
  return <DataGrid gridId="sales" dataSource={rows} rowKey="id" columns={columns} />;
}

Bir server component satırları çeker ve bir prop olarak aşağı geçirir; yalnızca <DataGrid>'i render eden yaprak 'use client''a ihtiyaç duyar. Stylesheet import'u aynı dosyada veya kök layout'ta yaşayabilir; ikisinden hangisini tercih ediyorsanız orada bir kez import edin.

Worker'ın çözülecek bir dosyaya ihtiyacı yok

Bir bileşenin SSR'dan vazgeçmesinin bir başka nedeni de bir Web Worker olabilir: bundler'lar worker URL'lerini build zamanında çözer, ve bu çözümlemeyi sunucuda yanlış yapmak kendi başına bir hata sınıfı. Worker'ı paketin içine derliyoruz ve çalışma zamanında bir Blob URL'inden başlatıyoruz, yani Next'in — ya da Vite'ın, ya da başka herhangi bir şeyin — bir sunucu render'ı sırasında çözmesi gereken bir worker dosyası yok. Yapılandıracak hiçbir şey yok, çünkü bulunacak hiçbir şey yok.

Yine de iki durum gerçek dosyayı istiyor, ve o iki durum için dosya paketin içinde gönderiliyor: worker-src … blob: içermeyen bir Content-Security- Policy, ve worker'ı bundle'ınıza gömülü değil, kendi başına önbelleklenen bir dosya olarak istemek. Her iki durumda da onu kendi origin'inizden sunun — tarayıcı başka bir alan adından gelen worker betiğini başlatmaz.

cp node_modules/@kanunilabs/datagrid-core/dist/datagrid.worker.js public/
<DataGrid worker={{ url: '/datagrid.worker.js' }} /* ... */ />

Worker herhangi bir nedenle başlayamazsa — kimsenin güncellemeyi hatırlamadığı sıkı bir CSP, Blob URL'lerinin kullanılamadığı bir ortam — grid bomboş kalmaz. Aynı sıralama ve filtreleme hattını ana iş parçacığında çalıştırır ve yalnızca development'ta, bir kez uyarı basar. Kullanıcı hiçbir şey fark etmez, tek fark çok büyük bir sıralamanın artık ana iş parçacığından zaman çalması, ondan taşınmak yerine.

Bir grid size ssr: false dayatıyorsa neyi kontrol etmeli

  • Bir effect'in dışında window, document veya bir ResizeObserver'a dokunuyor mu? Gerçek kısıt bu, ve "tüm bileşen sunucu tarafında render edilemiyor"dan daha dar.
  • Bundler'ınızın build zamanında çözmesi gereken bir worker dosyasına ihtiyacı var mı? Bu, vazgeçmek için ikinci, bağımsız bir neden, ve ya worker'ı derlenmiş halde göndererek ya da bundler'a onun için açık, statik bir import vererek çözülebilir.
  • Hydration öncesi markup gerçekte ne içeriyor? Boş bir placeholder ve gerçek başlıklarla render edilmiş bir iskelet, bir crawler için farklı sayfalardır — oturum açmış bir kullanıcı ikisinde de aynı son grid'i görse bile.

JavaScript kapalıyken deneyin, ya da sayfaya curl atın — başlık satırı ve sütun başlıkları zaten orada olmalı. DataGrid sayfamız gerçek bir grid'i sunucuda render ediyor, yani curl -s https://kanunilabs.com/datagrid | grep -o 'role="columnheader"' | wc -l sütun başlıklarını doğrudan HTML'den sayıyor. Başlıklar orada değilse, SSR desteği yanıt gövdesinde kontrol edebileceğiniz bir şey değil.


Satır penceresi, konteyneri ölçerek hydration sonrası dolar — aynı görüntü alanı matematiği sanal kaydırma yazısında anlatılıyor. Next.js, Vite ve diğer bundler'lar için tam kurulum SSR & bundler'lar sayfasında.

Etiketlermühendisliknextjsssr