Blog'a Dön

React 19.3: browser(), Trusted Types ve İlk Neyi Benimsemeli

React 19.3, View Transitions ve Fragment ref'lerini kararlı hale getiriyor; ama bu hafta bir ekibin listesine koyacağım değişiklikler browser() ve Trusted Types desteği.

Umut Korkmaz

Yayın tarihi: 4 dk okuma

React 19.3, 9 Eylül'de npm'e çıktı. Öne çıkan haber, geçen yıl deneysel olan <ViewTransition> ve Fragment ref'lerinin artık kararlı (stable) olması. Kod inceleyen biri olarak benim daha çok önemsediğim iki değişiklik ise daha küçük: react-dom içindeki browser() API'si, sunucuda render edilen uygulamalara "bu yalnızca tarayıcıda anlamlı" demenin resmi bir yolunu sunuyor. Trusted Types desteği de React'i katı bir Content Security Policy altında çalıştırmanın önündeki gerçek bir engeli kaldırıyor. React 19.3 sürüm yazısına göre nelerin değiştiğini ve benim ilk olarak ne yapacağımı aşağıda anlatıyorum.

browser(), isClient geçici çözümünü emekliye ayırıyor

Sunucuda render edilen pek çok kod tabanı aynı hack'in bir versiyonunu taşır: localStorage'ı ya da cihazın saat dilimini okuyan component'ler için useEffect içinde çevrilen bir mounted bayrağı veya bir typeof window !== 'undefined' kontrolü. 19.3 bunu birinci sınıf bir özellik haline getiriyor. Bir Client Component use(browser()) çağırdığında, React sunucu render'ı sırasında orada duruyor ve en yakın <Suspense> fallback'ini HTML'e yerleştiriyor. Tarayıcıda ise use(browser()) undefined döndürüyor ve component normal şekilde render ediliyor.

'use client';
import { Suspense, use } from 'react';
import { browser } from 'react-dom';

function RecentSearches() {
  use(browser('Reads recent searches from localStorage.'));
  const items = JSON.parse(localStorage.getItem('recent') ?? '[]');
  return <ul>{items.map((q: string) => <li key={q}>{q}</li>)}</ul>;
}

export function RecentSearchesSlot() {
  return (
    <Suspense fallback={<p>Loading recent searches...</p>}>
      <RecentSearches />
    </Suspense>
  );
}

Diğer use çağrıları gibi bir koşulun içinde ya da erken bir return'den sonra yer alabiliyor. Sürüm yazısında, yalnızca initialData verilmediğinde sunucu render'ından çıkan bir veri hook'u örneği gösteriliyor.

browser referans sayfasında bir code review kontrol listesine girmesi gereken uyarılar var:

  • Sunucu render'ı sırasında bir <Suspense> sınırının içinde olmalı. Böyle bir sınır yoksa sunucu render'ı başarısız oluyor.
  • Server Component'ten değil, bir Client Component'ten çağrılmalı.
  • browser()'ı tek başına çağırmak hiçbir şey yapmıyor. use'a verin; throw etmeyin.

Ayrıca isteğe bağlı bir gerekçe (reason) alıyor; bu gerekçe, sunucu renderer'ının onBrowserBailout callback'ine iletilen hatanın cause'u oluyor. Bu bailout'lar onError'ı atlıyor. Sunucu tarafında zaman aşımı olduğunda bekleyen sınırları istemciye devretmek için browser()'ı bir abort gerekçesi olarak da verebiliyorsunuz. En çok sevdiğim kısım bu gözlemlenebilirlik. Eski mounted bayrağı kalıbı görünmezdi: Bir sayfanın ne kadarının sessizce yalnızca istemci tarafına kaydığını size hiçbir şey söylemiyordu.

Trusted Types, katı bir CSP'yi gerçekçi kılıyor

Bir site Content-Security-Policy: require-trusted-types-for 'script' gönderdiğinde tarayıcı, innerHTML gibi injection sink'lerine verilen değerlerin ham string değil, kendi policy'leriniz üzerinden oluşturulmuş TrustedHTML, TrustedScript veya TrustedScriptURL nesneleri olmasını şart koşuyor. Şimdiye kadar React, değerleri DOM'a vermeden önce '' + value ile string'e çeviriyordu; bu da bir TrustedHTML nesnesini tarayıcının reddedeceği düz bir string'e geri dönüştürüyordu. Özenle bir sanitization policy'si yazabiliyordunuz ve React çıkışta bunu geri alıyordu. 19.3'te React bu nesneleri dönüştürmeden olduğu gibi geçiriyor.

Changelog'da bununla bağlantılı iki düzeltme daha var: nonce attribute'larındaki yanlış pozitif hydration uyuşmazlığı giderildi ve React artık değişmediği durumlarda innerHTML'i yeniden set etmiyor.

React policy'leri sizin yerinize yazmıyor; sanitization hâlâ sizin işiniz. Şu sıralar CEH'e çalışıyorum ve bu, üzerine tartışmaya devam etmektense açmayı tercih edeceğim türden bir derinlemesine savunma (defense-in-depth) kontrolü. Pratik yol şu: Direktifi önce report-only modunda yayına alın, her dangerouslySetInnerHTML'i tek bir policy'den geçirin ve hangi üçüncü taraf kodun DOM'a hâlâ string yazdığını raporların göstermesine izin verin.

View Transitions ve Fragment ref'leri kararlı

<ViewTransition>, tarayıcının View Transition API'sini kullanarak elemanları ekrana girerken, çıkarken, güncellenirken veya bir yerden başka bir yere taşınırken animasyonla gösteriyor. Yalnızca Transition olarak işaretlenen güncellemeler animasyonlu oluyor: startTransition, bir Suspense reveal'ı ya da useDeferredValue. addTransitionType, aynı state değişikliğinin nedenine göre farklı şekilde animasyonlanmasını sağlıyor; React bu türleri :active-view-transition-type() üzerinden CSS'e de açıyor. Suspense için öneri, yalnızca fallback'ten içeriğe geçiş güncellemesini animasyonlamak; update="auto" default="none" tam olarak bunu yapıyor. Şimdilik yalnızca DOM'a özgü, React Native desteği üzerinde çalışılıyor; dolayısıyla yayınladığım Expo uygulamalarına henüz bir faydası yok.

Fragment ref'leri, <Fragment>'e bir ref koymanızı sağlıyor ve size event listener'lar, focus/focusLast/blur, IntersectionObserver veya ResizeObserver için observeUsing, getClientRects ve scrollIntoView içeren bir FragmentInstance veriyor. Bu da yalnızca bir ref tutmak için eklenen sarmalayıcı <div>'i ortadan kaldırıyor ve ref'ini aktarmayan kütüphane component'lerinin etrafından dolaşmanızı sağlıyor.

Upgrade ticket'ı için daha küçük değişiklikler

  • Transition'lar artık bağımsız olarak render ediliyor; yavaş olan bir tanesi alakasız diğerlerini artık bekletmiyor.
  • Strict Mode artık hydration sırasında da Effect'leri iki kez çağırıyor; istemcide render edilen root'larla aynı davranış.
  • Server Component'ler, 'use client' modülünden import edilen bir Context'i Provider sarmalayıcısına gerek kalmadan doğrudan render edebiliyor.
  • Error.cause ve AggregateError.errors artık sunucudan istemciye taşınıyor.

Ben ne yapıyorum

  • typeof window ve mounted bayrağı kullanan effect'leri grep'leyip gerçekten yalnızca tarayıcıya özgü olanları bir gerekçe ve bir Suspense sınırıyla birlikte use(browser('...'))'e taşıyorum.
  • Bir uygulamada Trusted Types'ı report-only modunda açıp, zorunlu kılmaya karar vermeden önce bir haftalık ihlal kaydını okuyorum.
  • <ViewTransition>'ı bir migration olarak değil, isteğe bağlı bir cila olarak görüyorum.
  • Önce framework'ünüzün hangi React sürümünü getirdiğini kontrol edin. onBrowserBailout ve browser() abort gerekçesi, genellikle framework'ün sahip olduğu sunucu renderer'ının seçenekleri.

İlgiyi animasyon API'leri çekecek. Gerçek kodu temizleyenler ise daha sessiz değişiklikler.

Kaynaklar

Bu yazıda yararlanılan birincil kaynaklar.

  1. react.dev/blog/2026/09/09/react-19-3 (yeni sekmede açılır)
  2. react.dev/reference/react-dom/browser (yeni sekmede açılır)

Yazar hakkında

Umut Korkmaz

Lead Yazılım Mühendisi

İstanbul'da kıdemli yazılım mühendisi olarak çalışıyorum. Burada, bizzat canlıya aldığım araçları ve sürümleri, sahada öğrendiklerimle birlikte yazıyorum.

Ortak konulara göre seçildi.