Blog
Root Layout의 Provider 중첩을 Compose 패턴으로 정리하기
서론#
Next.js 프로젝트를 만들다 보면
전역에서 필요한 Provider가 점점 늘어난다.
처음에는 몇 개 되지 않기 때문에
root layout 안에 직접 중첩해도 크게 불편하지 않다.
하지만 기능이 추가되면서 Provider가 늘어나면
layout.tsx는 점점 읽기 어려워진다.
<Providers>
<PWAProvider>
<SnackBarProvider>
<TermsProvider>
<ToastProvider>
<MSWProvider />
<AuthBootstrap />
<WebPushProvider>
<NotificationSSEProvider>
<main className="w-full flex-1">{children}</main>
<Footer />
</NotificationSSEProvider>
</WebPushProvider>
</ToastProvider>
</TermsProvider>
</SnackBarProvider>
</PWAProvider>
</Providers>
이 구조는 동작에는 문제가 없지만,
Provider가 많아질수록 layout의 책임이 흐려진다.
이 글은
root layout에 직접 중첩되어 있던 Provider들을
Compose 패턴으로 정리한 과정을 기록한다.
문제였던 구조#
기존 root layout은
여러 Provider가 JSX 안에서 직접 중첩되어 있었다.
Provider는 전역 상태, 알림, 약관, PWA, Web Push, SSE처럼
앱 전체에 필요한 기능을 감싸는 역할을 한다.
문제는 Provider의 개수가 늘어나면서
layout.tsx가 다음 두 가지 책임을 동시에 가지게 된다는 점이었다.
- 페이지의 기본 구조를 정의하는 책임
- Provider 중첩 순서를 관리하는 책임
root layout은
앱의 가장 바깥 구조를 보여주는 파일이다.
그런데 Provider 중첩이 깊어지면
정작 중요한 main과 Footer 같은 요소가
Provider 구조 안에 묻히게 된다.
Compose 패턴으로 분리하기#
개선 후에는 Provider 조합을
AppProviders 컴포넌트로 분리했다.
<AppProviders>
<MSWProvider />
<AuthBootstrap />
<main className="w-full flex-1">{children}</main>
<Footer />
</AppProviders>
이제 layout.tsx에서는
앱 전체를 감싸는 Provider가 있다는 사실만 드러난다.
구체적으로 어떤 Provider들이 어떤 순서로 중첩되는지는
AppProviders 내부에서 관리한다.
AppProviders 구현#
Provider 목록은 배열로 관리했다.
type ProviderComponent = ComponentType<{ children: ReactNode }>;
const providers: ProviderComponent[] = [
QueryProviders,
PWAProvider,
SnackBarProvider,
ToastProvider,
TermsProvider,
WebPushProvider,
NotificationSSEProvider,
];
그리고 reduceRight를 사용해
배열의 순서대로 바깥에서 안쪽으로 Provider를 중첩했다.
const AppProviders = ({ children }: { children: ReactNode }) =>
providers.reduceRight<ReactNode>(
(acc, Provider) => <Provider>{acc}</Provider>,
children
);
reduceRight를 사용하는 이유는
JSX 중첩 구조와 배열 순서를 맞추기 위해서다.
예를 들어 배열이 다음과 같다면,
const providers = [AProvider, BProvider, CProvider];
결과는 다음 구조가 된다.
<AProvider>
<BProvider>
<CProvider>
{children}
</CProvider>
</BProvider>
</AProvider>
즉,
배열의 앞에 있는 Provider일수록
더 바깥쪽 Provider가 된다.
개선된 점#
이번 정리에서 느낀 개선점은
크게 세 가지였다.
1. 유지보수성#
Provider 관리가 단순해졌다.
새 Provider를 추가하거나 제거해야 할 때
layout.tsx의 JSX 중첩 구조를 다시 수정할 필요가 없다.
providers 배열만 수정하면 된다.
const providers: ProviderComponent[] = [
QueryProviders,
PWAProvider,
SnackBarProvider,
ToastProvider,
TermsProvider,
WebPushProvider,
NotificationSSEProvider,
];
Provider의 순서 역시
배열의 순서로 명확하게 관리할 수 있다.
2. 가독성 향상#
root layout이 훨씬 읽기 쉬워졌다.
기존에는 Provider 중첩을 따라가야
실제 페이지 구조를 확인할 수 있었다.
개선 후에는
layout.tsx에서 다음 요소들이 바로 드러난다.
- 전역 Provider
- MSW 초기화
- AuthBootstrap
- main 영역
- Footer
Provider 중첩 구조가 사라지면서
layout.tsx는 앱의 바깥 구조를 보여주는 역할에 더 가까워졌다.
3. 유연성#
Provider를 배열로 관리하면
구조를 바꾸는 비용도 줄어든다.
특정 Provider의 위치를 바꿔야 할 때
깊게 중첩된 JSX를 옮기는 대신
배열 안의 순서만 조정하면 된다.
또한 환경이나 조건에 따라
Provider 목록을 다르게 구성해야 하는 경우에도
배열 기반 구조가 더 다루기 쉽다.
주의할 점#
Compose 패턴으로 정리했다고 해서
Provider 순서의 중요성이 사라지는 것은 아니다.
Provider 중에는
다른 Provider의 context에 의존하는 경우가 있을 수 있다.
예를 들어 어떤 Provider가
QueryClient나 인증 상태를 내부에서 사용한다면,
그 Provider는 필요한 context보다 안쪽에 있어야 한다.
그래서 Provider를 배열로 정리할 때도
순서는 단순한 보기 좋음의 문제가 아니다.
의존 관계를 기준으로
바깥에서 안쪽 순서를 결정해야 한다.
참고한 글과 실제 적용 PR#
이번 정리는
React Provider Composition을 다룬 글을 참고하면서 진행했다.
해당 글에서는
여러 Provider가 피라미드처럼 중첩되는 문제를
배열 기반 composition으로 풀어내는 방식을 소개한다.
내 프로젝트에서는 이 아이디어를 그대로 복사하기보다는,
현재 root layout 구조에 맞게 AppProviders 컴포넌트로 분리했다.
실제 적용은 아래 PR에서 진행했다.
이 PR에서는
root layout에 직접 중첩되어 있던 Provider들을
AppProviders 내부의 providers 배열로 옮겼다.
결과적으로 layout.tsx는
페이지 구조를 보여주는 역할에 더 집중하게 되었고,
Provider 추가와 제거는 별도의 배열에서 관리할 수 있게 되었다.
정리#
Provider를 root layout에 직접 중첩하는 방식은
처음에는 단순하고 직관적이다.
하지만 Provider가 늘어나면
layout.tsx의 가독성이 떨어지고,
파일의 책임도 흐려진다.
이번 개선에서는
전역 Provider 조합을 AppProviders로 분리하고,
reduceRight를 이용해 Compose 패턴으로 정리했다.
그 결과 root layout은
앱의 전체 구조에 집중할 수 있게 되었고,
Provider 관리는 배열 하나로 명확하게 분리되었다.
이 리팩터링은 기능을 바꾸는 작업은 아니지만,
앱의 전역 구조를 더 읽기 쉽게 만드는 정리 작업에 가깝다.
관련 게시글
4개
서버 사이드 렌더링이란 무엇인가
서버 사이드 렌더링(SSR)이 무엇인지 개념부터 정리합니다. HTML을 서버에서 생성하는 방식과, 클라이언트 사이드 렌더링(CSR)과의 차이를 설명합니다.
Next.js에서 보안 헤더를 설정한다는 것의 의미와 한계
Next.js에서 설정할 수 있는 보안 헤더들의 역할과 한계를 정리합니다. 보안 헤더가 무엇을 막아주지 않는지, 프레임워크와 개발자의 책임 경계를 중심으로 설명합니다.
NEXT_PUBLIC_ 환경변수는 왜 env인데 클라이언트에 노출될까
NEXT_PUBLIC_ 환경변수가 왜 클라이언트 번들에 포함되는지, env는 언제 비밀이 되고 언제 설정값이 되는지 Next.js 빌드 구조와 보안 기준으로 정리합니다.
env는 보안인가? 많은 개발자들이 착각하는 이유
env는 보안 기능일까? 이 글에서는 env의 역할과 한계, NEXT_PUBLIC_ 환경 변수의 노출 특성, 프론트엔드에서 노출돼도 되는 값의 기준을 정리합니다.