NextJs için çoktandır var olan, ReactJs için 18 sürümü ile deneysel olarak gelen ve React 19 sürümünde stabil hale gelen server rendering olayının client rendering işlemlerini hepimiz kullanıyoruz. Ancak kullanırken yaptığımız bu işlemlerin bize maliyeti nedir sorusunu çoğu zaman kaçırıyoruz. Aslında ikisi arasında kullanıcı deneyimini artıracak çok derinlemesine bir fark ve birbirine bağımlılıklar var. İkisi arasındaki farklara ve bağımlılıklara başlamadan önce her birisinin ne olduğu anlamak ile başlayalım.
Client Rendering
Müşteri olarak anlamı olan client kullanıcının kendi bilgisayarında yaptığı işlemleri temsil etmektedir. Yani kullanıcı etkileşiminin olduğu bir yapı söz konusudur. Bu etkileşimler kullanıcının sayfa değiştirmesi, bir butona basması, faresi ile bir alan üzerinden geçerken bu alanın dışından içine doğru ve daha sonra tekrar dışına doğru gitmesi, sayfada kalması ya da girdikten sonra çıkması, sayfayı aşağı doğru kaydırması vb. sayabileceğimiz bir çok işlemdir. Bu etkileşimler tamamen kullanıcının o an sisteminize bağlı olduğu web sayfası ya da bir program aracılığıya sağlanır.
Server Rendering
Server sistemimizi barındırdığımız bir bilgisayar ortamıdır. Server rendering de bu server'da gerçekleşen olayları ifade eder. Server burada tamamen sadece sizin işlemlerinizi yapmak için özelleştirilmiş bir bilgisayardır. Sadece sizin işlemlerinizi yapıyor altındaki anlam o an sadece sizin talep ettiğiniz işleme odaklanıyor anlamına geliyor. (Gelen her istek için paralel işlemler yürütülüyor. Her gelen istek sıraya konulmuyor.) Bu sayede daha hızlı sonuçlar üretebiliyor. Keza bu özelleştirilmiş server'ların hızlı olan işlem güçleri yanında hızlı bir bağlantıları da var. Bu sayede kendi içinde oluşturduğu sonuçları daha hızlı bir şekilde verebiliyor.
Client Rendering ve Server Rendering Arasındaki Farklar
| Client Rendering | Server Rendering |
|---|---|
| İşlem gücü kullanıcının o anda sahip olduğu ram miktarına bağlıdır | İşlem gücü sunucunun ram miktarına bağlıdır ve tüm ram balı olan sistem için kullanılır. |
| Kullanıcı etkileşimli işler yapılabilir | Kullanıcı etkileşimli işler yapılamaz |
| Kullanıcı işlem süresince bekler | Kullanıcı işlem sonrası sonucu görür |
| Real Time ve Websocket desteklemez | Real Time ve Websocket destekler |
| API keyler kullanıcı tarafında olmasından dolayı güvenlik açığı oluşur | API keyler gizli kalır ve güvenliği atrırır. |
ReactJS'de Client Rendering ve Server Rendering Farkları
| Client Rendering | Server Rendering |
|---|---|
| use hookları sadece burada çalışır | use hookları çalışmaz |
| Js bundle süresince kullanıcı boş ekran görür | JS bundle yoktur. Kullanıcı HTML görür. |
| Fetch client olur ve açık şekilde takip edilebilir | Fetch sunucuda olur kullanıcı sadece sonucunu görür. |
Seçim Aşaması
Component ya da page yapısı fark etmeksizin yapıyı server olarak mı client olarak mı kullanacağımıza nasıl karar verecez. Bu nokta devreye giren bir kaç sorumuz var. Bunlar:
- Kullanıcı etkileşimi olmasını istiyor muyum?
- Server'dan(Sunucudan) gelecek data bir kullanıcı etkileşimin ardından mı olmalı yoksa hemen gelmesini istiyor muyum?
- Parent ve child component kurgusunda kullanıcı etkileşimi nerede olacak ve child component'ler birbirinden data transferini parent component ile (props drilling) alacak mı?
Bu sorunların cevaplarına göre aslında server rendering mi client rendering mi olmasını istediğimize karar verecez. Beraber bu soruları cevaplayalım.
- Kullanıcı etkileşimi olmasını istiyor muyum?
Kullanıcıdan bir butona basması, bir alan üzerine gelmesi vb etkileşimler bekliyorsam client rendering olması gerekiyor. O zaman da component yapımın en üstüne 'use client' directive'i eklenir. Bu component yapısı sıralı işlemleri yani asenkron async işlemleri ana yapısında destekleyemez ancak useEffect() içinde kullanılabilirler. Tüm veri çekme işlemleri useEffect() hooku ile gerçekleştirilir. useEffect() hook yapısı ise bir bağımlılığa ihtiyaç duymasından dolayı sadece etkileşim sonrası tetiklenir ve real time işlemler gerçekleşmez. Burada ana sebep işlemlerin birbirini beklememesi ve ilk gelen sonucun her zaman null olması dolayısıyla ilk işleminde null olma durumuna göre dönmesidir. Bu da state de tutulan verinin ilk başta null olması ve verinin geldiği süre zarfında null olarak kalmasındandır.
Kullanıcı etkileşimi yok ve sadece veri çekmek ya da veri göndermek üzerine bir işim varsa o zaman server rendering olması gerekiyor. NextJs ve ReactJs bir component oluşturulduğunda varsayılan olarak server component olarak görür bir şey yazılmasına gerek yoktur. Bir fonksiyon sadece server action olması isteniyorsa bu fonksiyon 'use server' directive ile işaretlenebilir. Bu component yapıları sıralı işlemleri yani asenkron async işlemleri doğal olarak destekler. Bunun en büyük artısı kuracağınız jsx ya da tsx yapınıza göre henüz gelmeyen veri için html yapısını loading component gösterebilir ya da hiç render etmeyebilirsiniz (Topluluk standartlarında loading component göstermek önemlidir.). Server component'lerde React 19 ile server componentlerde tam entegrasyona kavuşan <Suspense> ile veri çekme sürecinde kullanıcı deneyimi artırılabilir. Bunların hepsi iyileştirilmiş kullanıcı deneyimi sunar. Bir özellik olarak da veritabanında gerçekleşen real time işlemler yapılan ayarlamalarla server component'de değişiklik bir kullanıcı etkileşimi olmadan tetiklenebilir. Supabase ve Firebase gibi bulut sunucularında observation izlemesi ile. NodeJs, PHP, JAVA ve Python ile oluşturulan veritabanlarında websocket gibi sistemlerle sağlanır.
- Server'dan(Sunucudan) gelecek data bir kullanıcı etkileşimin ardından mı olmalı yoksa hemen gelmesini istiyor muyum?
Kullanıcı bir buton aracılığıyla ya da sayfa üzerinde bir alanla etkileşimi sonrası veri gelmesini istiyorsam client rendering olması gerektiğini gösterir. Ancak kullanıcıyı beklemeden gelmesi gerekiyorsa bu da server rendering olması gerektiği anlamına gelir. İlk soruda açıkladığımız olayların farklı bir soru ile cevabını aramak aslında.
- Parent ve child component kurgusunda kullanıcı etkileşimi nerede olacak ve child component'ler birbirinden data transferini parent component ile (props drilling) alacak mı?
Burada bu kararı vermeden önce bilinmesi gereken en önemli durum bir client component render olduğunda altındaki tüm child component'leri de tetikler ve onlarında yeniden render olmasına neden olur. Bu durum React.memo() ile sarmalayarak engellenebilir. Bu durum sadece bağımlılık yönetiminin iyi yapılması gerekmektedir. Bu bilgi ışığında kararımızı şu şekilde verebiliriz. Parent ve child componentlerimiz props drilling ne ölçüde gerek duyuyor?
Eğer props drillingde child component'ler sadece parent kısmında data bekliyorsa en mantıklı başlangıç parent componentin server component olmasıdır. Daha sonra child component'ler kendi içlerinde kullanıcı etkileşimi olup olmadığı sorusu gündeme gelir. Bu şekilde yukarıdan aşağıya doğru server component mi client component mi kararı verilir. Burada bir client component'in server altında yaşayabileceği ancak bir server component'in bir client altında server component özelliklerini gösteremeyeceği de unutulmamalıdır. Bu durumun istisnai durumu server component'in client component'e props olarak gönderilmesidir. Server component, client component'e children veya prop olarak geçirildiğinde server'da render edilmeye devam eder çünkü onu client component değil, üst katmandaki server context render etmiştir.
Eğer parent component child component'lerden bir tetiklenme bekliyorsa bu parent component ve tüm alt child component'lerin client component olması gerektiği anlamına gelecektir. Bu durum daha önce bahsettiğim parent component render olmasıyla tüm child component'lerin de render olmasına neden olur. Burada istisna React.memo() ile sarılmış componentler tetiklenmez. Özellikle useEffect() in bağımlılık kısmının boş geçilmesi durumunda sayfa her yenilendiğinde ya da bağımlılığın her değiştiğinde child component'lerde yeniden render olacaktır. Bu sebeple parent component de veriyi çekmek ve child componentlere göndermek yerine ilgili child component de veri çekmek daha akıllıca bir çözüm olacaktır.
Server ve Client Component'lerde Veri Çekmede Dikkat Edilmesi Gerekenler
- Parent component'i client render ile child component'lerde server render yapmamak
- Parent component de veriyi çekip ilgili child component'e yollamak yerine child component de çekmek. (Örnek olarak bir card list yapısına sahipseniz o card listi bir wrapper component içine alıp orada veriyi çekmek)
- Veri çekilecekse server component yapılması durumu var mı diye bakmak.
- Çekilen verilerin cache edilerek saklanması ve gereksiz rerender olaylarının tetiklenmemesi
- Tüm veriyi çektikten sonra filtreleme işlemi yapmak yerine veritabanından filtrelenmiş verinin çekilmesi. Yani amacına göre özelleştirilmiş backend sorgularının kullanılması.
- Component'lerin tek işlemi yerine getiren yapıda kurgulanması. Bu şekilde bağımlılık tetiklenmeleri bir iş için devreye girer.
useEffect()hookunda çok sayıda bağımlılığa bağlı bırakılmaması. İşlemin gerektirdiği bağımlılıkların verilmesi. Gerektiği durumlarda birden fazlauseEffect()ile değişimlerin sadece ilgili bağımlılıklarla tetiklenmesi.
Özet olarak kullanacağımız her ne olursa olsun süzgecimizden geçirdikten sonra ben buna gerçekten ihtiyacım var mı ve bunu başka şekilde de çözebilir miyim sorularını sürekli sorarak yukarıdan aşağı doğru kararları sürekli irdelemek gerekir. Bu işlemler hem dosya yapınızın hem de kod yapınızın okunabilirliği ve sürdürülebilirliğini önemli ölçüde artıracaktır. Kafanıza takılan sorular için iletişim sayfasında bana ulaşabilirsiniz.




