중첩 모달 문제를 라이브러리로 풀면서 배운 것들
react-layered-dialog를 만들고 공개하면서, 다이얼로그 문제를 풀 때 무엇을 남기고 무엇을 버려야 하는지 다시 정리했습니다.
용어 보기접기/펼치기
중첩 모달 문제를 풀겠다는 목표는 단순했습니다. 하지만 공개 라이브러리로 정리하는 과정에서는, 문제를 푸는 방식보다 무엇을 남길지를 결정하는 일이 더 중요했습니다.
들어가며
이전에 react-layered-dialog를 만들면서 겪었던 시행착오를 개발기 형태로 정리한 적이 있습니다. 당시에는 왜 이 라이브러리를 만들게 되었는지, 어떤 방향으로 API를 바꾸게 되었는지, 그리고 익숙한 사용성을 확보하기 위해 무엇을 참고했는지를 중심으로 적었습니다.
지금은 상황이 조금 달라졌습니다. 패키지는 npm에 공개되어 있고, 문서 사이트와 GitHub 저장소도 함께 운영하고 있습니다. 버전도 1.0.0까지 올라오면서, 단순히 만들었다는 사실보다 어떤 기준으로 라이브러리를 정리했는지가 더 중요해졌습니다.
이 글은 개발기의 연장선에 있지만, 조금 더 뒤에서 정리한 회고에 가깝습니다. 중첩 모달 문제를 라이브러리로 풀면서 결국 무엇을 배우게 되었는지, 그리고 공개한 뒤 어떤 지점이 더 중요해졌는지를 정리해 보려고 합니다.
문제는 모달 하나가 아니라 상태와 계층이었습니다
처음 문제의식은 단순했습니다. 화면 여기저기에 useState로 모달 상태를 흩뿌리고, 하위에서 또 다른 모달을 여는 순간 구조가 빠르게 지저분해졌습니다.
단순히 모달을 띄우는 일 자체는 어렵지 않습니다. 하지만 실제 서비스에서는 다음 문제가 한 번에 붙습니다.
- 어떤 모달이 가장 위에 있어야 하는지 관리해야 합니다.
- 확인, 취소, 입력 완료처럼 사용자 응답을 호출부로 다시 전달해야 합니다.
- 디자인 시스템은 프로젝트마다 다르지만, 상태 흐름은 반복해서 비슷한 문제를 만납니다.
결국 제가 풀고 싶었던 문제는 "모달 컴포넌트를 하나 더 만드는 일"이 아니었습니다. 상태, 계층, 호출 흐름을 반복 가능한 형태로 정리하는 일이었습니다.
라이브러리로 만들면서 끝까지 남긴 기준
개발을 진행하면서 이런저런 아이디어를 넣어봤지만, 지금 돌아보면 끝까지 살아남은 기준은 비교적 명확합니다.
1. UI보다 생명주기를 다루는 쪽이 맞았습니다
react-layered-dialog는 특정한 버튼 모양이나 애니메이션을 제공하지 않습니다. 이 라이브러리가 맡는 역할은 다이얼로그의 생명주기와 데이터 흐름입니다. 즉, 무엇을 어떻게 그릴지는 사용자에게 맡기고, 열기와 닫기, 제거, 결과 반환 같은 흐름만 관리하도록 정리했습니다.
이 판단 덕분에 라이브러리의 역할이 훨씬 선명해졌습니다. 디자인 시스템이 다른 프로젝트에도 들어갈 수 있고, 모달뿐 아니라 팝업이나 드로어처럼 다른 형태의 레이어 UI에도 대응하기 쉬워졌습니다.
2. 중첩 문제는 결국 z-index 문제가 아니라 순서 문제였습니다
처음에는 z-index를 어떻게 더 잘 계산할지에 집중하기 쉽습니다. 하지만 실제로 더 중요한 것은 "지금 어떤 다이얼로그가 어느 레이어에 올라와 있는가"를 일관된 상태로 관리하는 일입니다.
이 지점을 정리하고 나니, 자동 z-index 관리라는 기능도 단순한 편의 기능이 아니라 라이브러리의 핵심 역할이 되었습니다. 레이어 순서를 스토어가 알고 있어야 이후의 열기, 닫기, 포커스 흐름도 예측 가능해집니다.
3. 호출부의 사용성은 익숙해야 했습니다
아무리 구조가 좋아도 호출부가 낯설면 실제 프로젝트에서 쓰기 어렵습니다. 그래서 결과적으로는 "복잡한 내부 구현"보다 "익숙한 외부 사용성"을 더 중요하게 보게 되었습니다.
예를 들어 단순 알림은 즉시 열고 핸들을 받는 open 패턴이 더 자연스럽고, 확인 모달처럼 사용자 응답이 필요한 경우에는 await로 기다릴 수 있는 openAsync나 confirm 패턴이 더 잘 맞습니다.
이 둘을 분리해 두니 라이브러리 사용 방식이 훨씬 설명 가능해졌습니다. 지금 문서에서도 이 구분은 핵심 개념으로 남아 있습니다.
const result = await dialog.confirm({
title: "정말 삭제할까요?",
message: "삭제된 데이터는 복구할 수 없습니다.",
});
if (!result.ok) return;
await deleteItem();공개하고 나서 더 중요해진 문제도 있었습니다
패키지를 배포하고 문서를 만들기 전에는 API가 동작하는지만 보게 됩니다. 그런데 공개 라이브러리가 되면 그다음부터는 다른 종류의 문제들이 더 중요해집니다.
SSR과 Next.js에서는 안전한 기본값이 더 중요했습니다
이 라이브러리를 정리하면서 가장 강조하게 된 주제 중 하나는 SSR 안전성입니다. 특히 Next.js App Router 환경에서는 서버에서 전역 스토어를 생성하면 요청 간 상태 공유로 이어질 수 있습니다.
이건 단순한 사용 팁이 아니라, 라이브러리를 소개할 때 반드시 같이 설명해야 하는 전제조건에 가깝습니다. 실제로 문서에서도 SSR 가이드를 별도로 두고, 브라우저에서만 스토어를 생성하는 패턴을 강하게 설명하고 있습니다.
여기서 배운 점은 분명했습니다. 라이브러리를 공개한다는 것은 좋은 API를 제공하는 일만이 아니라, 위험한 사용 방식을 명시적으로 막아주는 일이기도 합니다.
문서가 부족하면 API는 사실상 없는 것과 비슷했습니다
지금 react-layered-dialog에는 소개, 빠른 시작, 기본 사용법, 비동기 다이얼로그, SSR, Next.js Provider, 마이그레이션 문서까지 분리되어 있습니다.
개발을 할 때는 코드를 먼저 보게 되지만, 실제 사용자는 README와 문서부터 봅니다. 그래서 문서 구조를 잡는 일은 부가 작업이 아니라, 라이브러리의 사용성을 완성하는 작업이라는 점을 체감하게 되었습니다.
v1에서는 기능 추가보다 이름과 경계를 정리하는 일이 더 중요했습니다
1.0.0을 지나오면서 느낀 점도 비슷합니다. 메이저 버전이라고 해서 무언가를 크게 더 넣었다기보다, 오히려 공개 API의 이름과 경계를 더 분명하게 정리하는 일이 중요했습니다.
예를 들어 핸들 구조에서 .dialog를 .ref로 정리하고, 관련 타입명을 DialogHandle, DialogRef로 맞춘 변화는 내부적으로는 작아 보일 수 있습니다. 하지만 외부 사용자가 이해하고 기억해야 하는 모델을 더 명확하게 만든다는 점에서는 오히려 이런 변화가 더 중요했습니다.
공개 라이브러리에서 일관된 이름은 기능 못지않게 중요합니다. 이름이 흔들리면 개념도 흔들리기 때문입니다.
지금 기준에서 react-layered-dialog를 어떻게 설명하고 싶은가
지금의 저는 이 라이브러리를 "중첩 모달을 쉽게 여는 도구"라고만 설명하고 싶지는 않습니다. 조금 더 정확하게는, 레이어 UI의 생명주기와 호출 흐름을 선언적으로 정리하기 위한 도구라고 설명하고 싶습니다.
이 설명이 중요한 이유는 범위를 분명하게 해주기 때문입니다.
- 이 라이브러리는 디자인 시스템을 대체하지 않습니다.
- 대신 상태 흐름, 레이어 순서, 결과 반환, 호출 방식 같은 문제를 담당합니다.
- 그래서 Headless 구조와 타입 안전한 레지스트리, 동기/비동기 패턴 분리가 핵심이 됩니다.
지금 README에서 아키텍처와 메서드 비교를 먼저 보여주는 것도 같은 이유입니다. 이제는 단순히 "되게 만들었다"보다 "어떤 문제를 맡고 어떤 문제는 맡지 않는지"를 먼저 설명하는 편이 맞다고 판단했습니다.
마치며
중첩 모달 문제를 해결하고 싶다는 출발점 자체는 여전히 유효합니다. 다만 공개 라이브러리로 정리하고 나니, 실제로 더 오래 남는 것은 구현 세부사항보다 설계 기준이라는 점을 더 분명하게 느끼게 되었습니다.
지금 돌아보면 이 프로젝트를 통해 배운 가장 큰 점은 하나입니다. 라이브러리는 기능을 쌓아 올리는 작업이 아니라, 남길 것과 버릴 것을 계속 정리해 가는 작업에 더 가깝습니다.
react-layered-dialog도 아직 완성된 도구라고 생각하지는 않습니다. 하지만 적어도 지금은 어떤 문제를 풀고 있는지, 어떤 환경에서 조심해야 하는지, 그리고 어떤 사용성을 지키고 싶은지는 이전보다 훨씬 분명하게 설명할 수 있습니다.
참고
- react-layered-dialog npm: 현재 배포 버전과 패키지 정보를 확인할 수 있습니다.
- react-layered-dialog 문서: 소개, 빠른 시작, SSR 가이드를 포함한 공식 문서입니다.
- react-layered-dialog GitHub 저장소: 구현 코드와 변경 이력을 확인할 수 있습니다.