C.W.K.
Stream
Lesson 07 of 08 · published

Composition Pattern

~20 min · composition, providers, children

Level 0Curious
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

Pattern 1: Server 가 Client wrap (children)

가장 흔한 모양. Server Component 가 data fetch 하고 Client Component render, 다른 Server Component 를 children 으로 넘김. Client wrapper 가 interactivity 처리; children 은 server-rendered 유지.

Pattern 2: Context provider

React Context 가 Client Component 필요 (hook 사용). Thin 'use client' provider 만들어 root layout 안에 둬. 나머지는 server 유지 가능.

Pattern 3: Prop 으로 slot

더 의도적 composition (예: header/body/footer 가 server-side render 돼야 하는 modal) 위해, 단일 children 말고 multiple ReactNode prop 받음. Client Component 가 배치만 하고 render 안 함.

Code

Pattern 1: server parent + client wrapper + server children·tsx
// app/page.tsx — Server
import { Disclosure } from '@/components/Disclosure';
import { TermsContent } from '@/components/TermsContent';

export default async function Page() {
  const terms = await loadTerms(); // Server fetch
  return (
    <Disclosure label="Read the terms">
      <TermsContent text={terms} />  {/* Server-rendered subtree */}
    </Disclosure>
  );
}

// components/Disclosure.tsx — Client wrapper
'use client';
import { useState } from 'react';
export function Disclosure({ label, children }: { label: string; children: React.ReactNode }) {
  const [open, setOpen] = useState(false);
  return (
    <details open={open} onToggle={(e) => setOpen(e.currentTarget.open)}>
      <summary>{label}</summary>
      {children}
    </details>
  );
}
Pattern 2: thin Client provider in root layout·tsx
// providers/ThemeProvider.tsx — Client
'use client';
import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext<'light' | 'dark'>('dark');
export const useTheme = () => useContext(ThemeContext);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme] = useState<'light' | 'dark'>('dark');
  return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
}

// app/layout.tsx — Server, 모든 것 wrap
import { ThemeProvider } from '@/providers/ThemeProvider';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en"><body>
      <ThemeProvider>{children}</ThemeProvider>
    </body></html>
  );
}

External links

Exercise

Tab 그리는 Client Component <TabPanel> 만들되 각 tab 의 content 를 server-rendered children 으로 받게. Tab body component 가 client bundle 에 안 들어가는지 보여줘.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.