
개요
Next.js(App Router)로 개발을 하다 보면 Context API, Query Client, 또는 Theme Provider를 설정할 때 한 번쯤 이런 에러를 마주치게 됩니다.
"Error: Next.js Server Components do not support Context."
보통 layout.tsx에 바로 Provider를 작성하려 할 때 발생합니다. 이번 글에서는 왜 layout.tsx를 별도의 Providers 컴포넌트로 감싸야 하는지, 그 구조적 이유와 이점에 대해서 공부한 내용을 정리해보고자 합니다.
왜 에러가 날까❓
Next.js의 App Router는 기본적으로 모든 컴포넌트를 서버 컴포넌트(Server Components)로 간주합니다.
하지만 React Context, Redux, React Query, Theme Provider 같은 상태 관리 라이브러리들은 클라이언트 사이드에서만 동작합니다.
이들은 React의 useState, useEffect 같은 훅을 사용하기 때문입니다. 여기서 문제가 발생합니다. layout.tsx는 기본적으로 Server Component인데, 클라이언트 전용 기능을 직접 사용할 수 없습니다.
즉, layout.tsx는 애플리케이션의 뼈대를 담당하는 서버 컴포넌트인데, 여기에 직접 createContext나 Provider를 넣으면 "서버 컴포넌트에서 클라이언트 기능을 쓰려고 하네?"라며 에러를 뱉는 것입니다.
해결책: Providers 컴포넌트 분리
이 문제를 해결하기 위해 별도의 providers.tsx 파일을 만들고 'use client' 지시어를 추가합니다.
이렇게 하면 해당 컴포넌트와 그 자식들만 클라이언트 컴포넌트로 동작하게 됩니다.
구현 순서
- providers.tsx 생성
- 상단에 'use client' 지시어를 붙여 이 컴포넌트가 클라이언트 사이드에서 동작함을 선언합니다.
- layout.tsx에서 주입
- 서버 컴포넌트인 레이아웃에서 위에서 만든 Providers로 {children}을 감쌉니다.
◽app/providers.tsx

◽app/ layout.tsx

이 패턴의 장점
1. 서버 컴포넌트의 이점 유지
- layout.tsx 자체를 'use client'로 바꿀 수도 있지만, 그렇게 되면 레이아웃과 그 하위의 모든 컴포넌트가 서버 컴포넌트로서의 이점(SEO 최적화, 번들 사이즈 감소 등)을 잃게 됩니다.
- Providers로만 감싸면, 레이아웃은 서버 컴포넌트로 남기면서 내부의 상태만 클라이언트에서 관리할 수 있습니다.
2. Children은 서버 컴포넌트일 수 있다.
- "클라이언트 컴포넌트(Providers)의 children으로 전달되는 서버 컴포넌트는 여전히 서버 컴포넌트로 동작한다"는 특성 덕분입니다.
- 따라서 전체 페이지를 감싸더라도 각 페이지(page.tsx) 안의 로직들은 여전히 서버에서 빠르게 처리될 수 있습니다.
3. 관심사의 분리
- 전역 상태 관리 로직을 별도 파일로 분리하여 코드 가독성과 유지보수성이 향상됩니다.
4. 일관된 상태 유지
- 전역 상태(Redux, React Query, Auth 등)는 앱 전체에서 공유되어야 합니다.
- layout.tsx는 페이지가 바뀌어도 리렌더링되지 않고 유지되는 특성이 있어, 이곳에 Provider를 배치하는 것이 상태를 유실하지 않는 가장 안정적인 방법입니다.
children은 어떻게 서버 컴포넌트로 유지될까❓
여기서 저는 한 가지 의문이 생겼습니다.
Providers가 클라이언트 컴포넌트('use client')라면, 그 안에 감싸진 자식들도 전부 클라이언트 컴포넌트가 되는 것 아닌가?
결론부터 말하자면 이는 "아니다"라는게 맞습니다. 그 이유에 대해서 알아보겠습니다.
Composition Pattern이란?
- React의 Composition Pattern은 컴포넌트를 import해서 직접 사용하는 것과 props(children)로 전달받아 사용하는 것을 구분합니다.
Import 방식 (서버 -> 클라이언트 변환됨) ❌

클라이언트 컴포넌트가 서버 컴포넌트를 직접 import하면, 서버 컴포넌트도 클라이언트 컴포넌트로 변환됩니다.
Composition 방식 (서버 컴포넌트 유지) ✅


이 경우 ServerComponent는 서버에서 먼저 렌더링되고, 그 결과가 ClientComponent에 props로 전달됩니다.
왜 이런 차이가 생길까❓
◽Composition Pattern
- 서버: layout.tsx가 실행됨
- 서버: <ServerComponent />가 렌더링됨 → RSC Payload 생성
- 서버: 렌더링 결과를 children props로 전달
- 클라이언트: ClientComponent가 받은 children을 그대로 표시
◽Import Pattern
- 클라이언트: ClientComponent가 실행됨
- 클라이언트: ServerComponent를 import했으므로 여기서 렌더링해야 함
- 결과: 서버 컴포넌트가 클라이언트에서 실행됨 (변환됨)
따라서, 클라이언트 컴포넌트(Providers)의 children으로 전달되는 서버 컴포넌트는 여전히 서버 컴포넌트로 동작합니다.
결론
layout.tsx를 Providers로 감싸는 패턴은 Next.js App Router에서 서버와 클라이언트의 장점을 모두 살리는 핵심 패턴입니다.
Composition Pattern 덕분에 전역 상태 관리와 서버 사이드 렌더링을 동시에 활용할 수 있고, 성능 최적화와 개발 경험 모두를 향상시킬 수 있습니다.