Server and Client Composition Patterns

React uygulamaları oluştururken, uygulamanızın hangi bölümlerinin sunucuda veya istemcide işlenmesi gerektiğini göz önünde bulundurmanız gerekecektir. Bu sayfa, Sunucu ve İstemci Bileşenlerini kullanırken önerilen bazı kompozisyon modellerini kapsar.

When to use Server and Client Components?

İşte Sunucu ve İstemci Bileşenleri için farklı kullanım durumlarının hızlı bir özeti:

What do you need to do? Server Component Client Component
Fetch data
Access backend resources (directly)
Keep sensitive information on the server (access tokens, API keys, etc)
Keep large dependencies on the server / Reduce client-side JavaScript
Add interactivity and event listeners (onClick(), onChange(), etc)
Use State and Lifecycle Effects (useState(), useReducer(), useEffect(), etc)
Use browser-only APIs
Use custom hooks that depend on state, effects, or browser-only APIs
Use React Class components

Server Component Patterns

İstemci tarafı oluşturmayı tercih etmeden önce, sunucuda veri alma veya veritabanınıza ya da arka uç hizmetlerinize erişme gibi bazı işler yapmak isteyebilirsiniz.

Sunucu Bileşenleri ile çalışırken bazı yaygın kalıplar aşağıda verilmiştir:

Sharing data between components

Sunucudan veri alırken, verileri farklı bileşenler arasında paylaşmanız gereken durumlar olabilir. Örneğin, aynı verilere bağlı olan bir düzeniniz ve bir sayfanız olabilir.

React Context (sunucuda mevcut değildir) kullanmak veya verileri prop olarak iletmek yerine fetch veya React'in cache işlevini kullanarak aynı veriyi, aynı veri için yinelenen isteklerde bulunma endişesi olmadan, ona ihtiyaç duyan bileşenlerden alabilirsiniz. Bunun nedeni, React'in veri isteklerini otomatik olarak memoize etmek için fetch 'u genişletmesi ve fetch mevcut olmadığında cache işlevinin kullanılabilmesidir.

React'te memoization hakkında daha fazla bilgi edinin.

Keeping Server-only Code out of the Client Environment

JavaScript modülleri hem Sunucu hem de İstemci Bileşenleri modülleri arasında paylaşılabildiğinden, yalnızca sunucuda çalıştırılması amaçlanan kodun istemciye gizlice girmesi mümkündür.

Örneğin, aşağıdaki veri alma fonksiyonunu ele alalım:

lib/data.ts
export async function getData() {
  const res = await fetch('https://external-service.com/data', {
    headers: {
      authorization: process.env.API_KEY,
    },
  })
 
  return res.json()
}

İlk bakışta, getData hem sunucu hem de istemci üzerinde çalışıyor gibi görünmektedir. Ancak, bu fonksiyon sadece sunucuda çalıştırılmak üzere yazılmış bir API_KEY içermektedir.

API_KEY ortam değişkeni NEXT_PUBLIC ile ön ek almadığından, yalnızca sunucudan erişilebilen özel bir değişkendir. Ortam değişkenlerinizin istemciye sızdırılmasını önlemek için Next.js özel ortam değişkenlerini boş bir dizeyle değiştirir.

Sonuç olarak, getData() içe aktarılabilse ve istemcide çalıştırılabilse bile, beklendiği gibi çalışmayacaktır. Değişkeni herkese açık hale getirmek işlevin istemcide çalışmasını sağlayacak olsa da, hassas bilgileri istemciye ifşa etmek istemeyebilirsiniz.

Sunucu kodunun bu tür istenmeyen istemci kullanımını önlemek için, server-only paketini kullanarak diğer geliştiricilerin bu modüllerden birini yanlışlıkla bir İstemci Bileşenine aktarmaları durumunda derleme zamanı hatası vermelerini sağlayabiliriz.

server-only adresini kullanmak için önce paketi yükleyin:

Terminal
npm install server-only

Ardından paketi yalnızca sunucu kodu içeren herhangi bir modüle içe aktarın:

lib/data.js
import 'server-only'
 
export async function getData() {
  const res = await fetch('https://external-service.com/data', {
    headers: {
      authorization: process.env.API_KEY,
    },
  })
 
  return res.json()
}

Artık, getData() adresini içe aktaran herhangi bir İstemci Bileşeni, bu modülün yalnızca sunucuda kullanılabileceğini açıklayan bir derleme zamanı hatası alacaktır.

İlgili paket client-only, yalnızca istemci kodu içeren modülleri işaretlemek için kullanılabilir - örneğin, window nesnesine erişen kod.

Using Third-party Packages and Providers

Sunucu Bileşenleri yeni bir React özelliği olduğundan, ekosistemdeki üçüncü taraf paketler ve sağlayıcılar useState, useEffect ve createContext gibi yalnızca istemci özelliklerini kullanan bileşenlere "use client" yönergesini eklemeye yeni başlıyor.

Günümüzde, yalnızca istemci özelliklerini kullanan npm paketlerindeki birçok bileşen henüz yönergeye sahip değildir. Bu üçüncü taraf bileşenler, "use client" yönergesine sahip oldukları için İstemci Bileşenleri içinde beklendiği gibi çalışacak, ancak Sunucu Bileşenleri içinde çalışmayacaktır.

Örneğin, <Carousel /> bileşenine sahip varsayımsal acme-carousel paketini yüklediğinizi varsayalım. Bu bileşen useState adresini kullanır, ancak henüz "use client" yönergesine sahip değildir.

Bir İstemci Bileşeni içinde <Carousel /> adresini kullanırsanız, beklendiği gibi çalışacaktır:

app/gallery.tsx
'use client'
 
import { useState } from 'react'
import { Carousel } from 'acme-carousel'
 
export default function Gallery() {
  let [isOpen, setIsOpen] = useState(false)
 
  return (
    <div>
      <button onClick={() => setIsOpen(true)}>View pictures</button>
 
      {/* Works, since Carousel is used within a Client Component */}
      {isOpen && <Carousel />}
    </div>
  )
}

Ancak, bunu doğrudan bir Sunucu Bileşeni içinde kullanmaya çalışırsanız bir hata görürsünüz:

app/page.tsx
import { Carousel } from 'acme-carousel'
 
export default function Page() {
  return (
    <div>
      <p>View pictures</p>
 
      {/* Error: `useState` can not be used within Server Components */}
      <Carousel />
    </div>
  )
}

Bunun nedeni Next.js'nin <Carousel /> 'un yalnızca istemci özelliklerini kullandığını bilmemesidir.

Bunu düzeltmek için, yalnızca istemci özelliklerine dayanan üçüncü taraf bileşenleri kendi İstemci Bileşenlerinize sarabilirsiniz:

app/carousel.tsx
'use client'
 
import { Carousel } from 'acme-carousel'
 
export default Carousel

Artık <Carousel /> adresini doğrudan bir Sunucu Bileşeni içinde kullanabilirsiniz:

app/page.tsx
import Carousel from './carousel'
 
export default function Page() {
  return (
    <div>
      <p>View pictures</p>
 
      {/*  Works, since Carousel is a Client Component */}
      <Carousel />
    </div>
  )
}

Çoğu üçüncü taraf bileşenini sarmalamanız gerekeceğini düşünmüyoruz, çünkü bunları büyük olasılıkla İstemci Bileşenleri içinde kullanacaksınız. Bununla birlikte, React state ve context'e dayandıkları ve genellikle bir uygulamanın kökünde ihtiyaç duydukları için sağlayıcılar bir istisnadır. Üçüncü taraf bağlam sağlayıcıları hakkında daha fazla bilgiyi aşağıda bulabilirsiniz.

Using Context Providers

Context sağlayıcıları, geçerli tema gibi global endişeleri paylaşmak için genellikle bir uygulamanın köküne yakın bir yerde oluşturulur. React bağlamı Sunucu Bileşenlerinde desteklenmediğinden, uygulamanızın kökünde bir bağlam oluşturmaya çalışmak hataya neden olacaktır:

app/layout.tsx
import { createContext } from 'react'
 
//  createContext is not supported in Server Components
export const ThemeContext = createContext({})
 
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
      </body>
    </html>
  )
}

Bunu düzeltmek için, bağlamınızı oluşturun ve sağlayıcısını bir İstemci Bileşeninin içinde oluşturun:

app/theme-provider.tsx
'use client'
 
import { createContext } from 'react'
 
export const ThemeContext = createContext({})
 
export default function ThemeProvider({
  children,
}: {
  children: React.ReactNode
}) {
  return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
}

Sunucu Bileşeniniz artık bir İstemci Bileşeni olarak işaretlendiğinden sağlayıcınızı doğrudan oluşturabilecektir:

app/layout.tsx
import ThemeProvider from './theme-provider'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  )
}

Sağlayıcı kökte işlendiğinde, uygulamanızdaki diğer tüm İstemci Bileşenleri bu bağlamı kullanabilecektir.

Bilmekte fayda var: Sağlayıcıları ağaçta mümkün olduğunca derinde oluşturmalısınız - ThemeProvider adresinin <html> belgesinin tamamı yerine yalnızca {children} adresini nasıl sardığına dikkat edin. Bu, Next.js'nin Sunucu Bileşenlerinizin statik kısımlarını optimize etmesini kolaylaştırır.

Advice for Library Authors

Benzer bir şekilde, diğer geliştiriciler tarafından tüketilecek paketler oluşturan kütüphane yazarları, paketlerinin istemci giriş noktalarını işaretlemek için "use client" yönergesini kullanabilirler. Bu, paket kullanıcılarının paket bileşenlerini bir sarmalama sınırı oluşturmak zorunda kalmadan doğrudan kendi Sunucu Bileşenlerine aktarmalarını sağlar.

Ağacın daha derinlerinde 'use client' kullanarak paketinizi optimize edebilir ve içe aktarılan modüllerin Sunucu Bileşeni modül grafiğinin bir parçası olmasına izin verebilirsiniz.

Bazı paketleyicilerin "use client" yönergelerini çıkarabileceğini belirtmek gerekir. React Wrap Balancer ve Vercel Analytics depolarında esbuild'in "use client" yönergesini içerecek şekilde nasıl yapılandırılacağına dair bir örnek bulabilirsiniz.

Client Components

Moving Client Components Down the Tree

İstemci JavaScript paketinin boyutunu azaltmak için İstemci Bileşenlerini bileşen ağacınızda aşağıya taşımanızı öneririz.

Örneğin, statik öğeler (örn. logo, bağlantılar, vb.) ve durum kullanan etkileşimli bir arama çubuğu içeren bir Düzeniniz olabilir.

Tüm düzeni bir İstemci Bileşeni yapmak yerine, etkileşimli mantığı bir İstemci Bileşenine taşıyın (örneğin <SearchBar />) ve düzeninizi bir Sunucu Bileşeni olarak tutun. Bu, düzenin tüm bileşen Javascript'ini istemciye göndermek zorunda olmadığınız anlamına gelir.

app/layout.tsx
// SearchBar is a Client Component
import SearchBar from './searchbar'
// Logo is a Server Component
import Logo from './logo'
 
// Layout is a Server Component by default
export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <nav>
        <Logo />
        <SearchBar />
      </nav>
      <main>{children}</main>
    </>
  )
}

Passing props from Server to Client Components (Serialization)

Bir Sunucu Bileşeninde veri alırsanız, verileri İstemci Bileşenlerine prop olarak aktarmak isteyebilirsiniz. Sunucudan İstemci Bileşenlerine aktarılan prop'ların React tarafından serileştirilebilir olması gerekir.

İstemci Bileşenleriniz serileştirilemeyen verilere bağlıysa, üçüncü taraf bir kütüphane ile istemciden veya bir Rota İşleyicisi aracılığıyla sunucudan veri getirebilirsiniz.

Interleaving Server and Client Components

İstemci ve Sunucu Bileşenlerini bir araya getirirken, kullanıcı arayüzünüzü bir bileşen ağacı olarak görselleştirmek yararlı olabilir. Bir Sunucu Bileşeni olan kök düzeninden başlayarak, "use client" yönergesini ekleyerek istemcide bileşenlerin belirli alt ağaçlarını oluşturabilirsiniz.

Bu istemci alt ağaçları içinde, Sunucu Bileşenlerini yuvalayabilir veya Sunucu Eylemlerini çağırabilirsiniz, ancak akılda tutulması gereken bazı şeyler vardır:

Unsupported Pattern: Importing Server Components into Client Components

Aşağıdaki model desteklenmez. Bir Sunucu Bileşenini bir İstemci Bileşenine aktaramazsınız:

app/client-component.tsx
'use client'
 
// You cannot import a Server Component into a Client Component.
import ServerComponent from './Server-Component'
 
export default function ClientComponent({
  children,
}: {
  children: React.ReactNode
}) {
  const [count, setCount] = useState(0)
 
  return (
    <>
      <button onClick={() => setCount(count + 1)}>{count}</button>
 
      <ServerComponent />
    </>
  )
}

Supported Pattern: Passing Server Components to Client Components as Props

Aşağıdaki model desteklenmektedir. Sunucu Bileşenlerini bir İstemci Bileşenine prop olarak aktarabilirsiniz.

Yaygın bir model, İstemci Bileşeninizde bir "yuva" oluşturmak için React children prop'unu kullanmaktır.

Aşağıdaki örnekte, <ClientComponent> bir children prop kabul etmektedir:

app/client-component.tsx
'use client'
 
import { useState } from 'react'
 
export default function ClientComponent({
  children,
}: {
  children: React.ReactNode
}) {
  const [count, setCount] = useState(0)
 
  return (
    <>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      {children}
    </>
  )
}

<ClientComponent> children adresinin eninde sonunda bir Sunucu Bileşeninin sonucu tarafından doldurulacağını bilmez. <ClientComponent> 'un sahip olduğu tek sorumluluk children 'un eninde sonunda nereye yerleştirileceğine karar vermektir.

Bir üst Sunucu Bileşeninde, hem <ClientComponent> hem de <ServerComponent> öğelerini içe aktarabilir ve <ServerComponent> öğesini <ClientComponent> öğesinin bir alt öğesi olarak geçirebilirsiniz:

app/page.tsx
// This pattern works:
// You can pass a Server Component as a child or prop of a
// Client Component.
import ClientComponent from './client-component'
import ServerComponent from './server-component'
 
// Pages in Next.js are Server Components by default
export default function Page() {
  return (
    <ClientComponent>
      <ServerComponent />
    </ClientComponent>
  )
}

Bu yaklaşımla <ClientComponent> ve <ServerComponent> birbirinden ayrılır ve bağımsız olarak render edilebilir. Bu durumda, <ServerComponent> alt öğesi, <ClientComponent> istemcide oluşturulmadan çok önce sunucuda oluşturulabilir.

Bilmekte fayda var:

  • "İçeriği yukarı kaldırma" modeli, bir üst bileşen yeniden render edildiğinde iç içe geçmiş bir alt bileşenin yeniden render edilmesini önlemek için kullanılmıştır.
  • children prop'u ile sınırlı değilsiniz. JSX iletmek için herhangi bir prop kullanabilirsiniz.