현재 댄스 스튜디오 관리 서비스 Dio를 개발 중이다. 댄스 스튜디오 관리 서비스인 만큼 댄스 클래스 예약 기능은 서비스 핵심 기능 중 하나이다. 그러므로 프론트엔드에서 클래스 예약 페이지의 중요성도 그만큼 높다. 클래스 예약 페이지는 아래와 같은 모습이다.
이 페이지를 구현하면서 만족시키고 싶었던 조건은 아래와 같다.
1. url을 통해서 모달이 띄워진 상태로 바로 페이지에 접근할 수 있을 것.
(스튜디오 클래스 스케쥴 전체를 조회하고 싶은 유저도 있겠지만, 특정 클래스 스케쥴을 조회하고 싶어하는 유저들도 많을 것이기 때문. 특히 관리자들의 경우 특정 클래스의 링크를 바로 조회하고 싶은 경우가 많은 것이다. 모쪼록 스케쥴 리스트 페이지로 접근했을 때는, 원하는 클래스 스케쥴이 정확히 어디있는지 찾기 어려울 수 있다.)
2. 유저가 예약을 완료하기까지의 페이지 이동을 최소화할 것.
(물론 이때 "페이지 이동"이란 유저에게 보이는 화면 상의 이동을 말한다.)
Next.js App Router를 사용하고 있기에, 정적인 정보가 담긴 페이지들은 가급적이면 Server Component로 렌더링을 해줘서 초기 로드 속도도 높이고, SEO 최적화에도 좋은 영향을 주기 위해, 최소 단위에서 "use client"를 사용해주고 싶었다.
그래서 아래와 같이 searchParams에서 클래스 스케쥴 아이디를 조회해와서 SSR로 렌더링하는 방식으로 구현해주었다.
const StudioSchedulePage = async ({ params: { lang, id }, searchParams }: PageProps) => {
const scheduleId = searchParams && searchParams[SCHEDULE_PARAMS_KEY];
const sessionId = searchParams && searchParams[SESSION_PARAMS_KEY];
const studioDetailData = await PlatformStudiosService.getStudio(Number(id));
return (
<div className={styles['studio-schedule-page']}>
{scheduleId && (
<ClassModal
scheduleId={Number(scheduleId)}
sessionId={!isNil(sessionId) ? Number(sessionId) : undefined}
/>
)}
// (생략)
그런데 아래와 같은 문제가 발생했다.
스케쥴 목록 캘린더 뷰에서, 원하는 클래스 스케쥴을 클릭했을 때, 모달을 띄우기까지 걸리는 시간이 자그마치, 2.7s나 걸렸다. ms가 아니라 s...
2.7s의 로딩이 더욱 치명적이었던 것은 따로 로딩 상태를 표시해주지 못해서, 유저가 로딩되고 있다는 것을 인지할 수 없다는 점이었다. 유저 입장에서는 에러라고 느낄 수 있을 법한 부분이다. 로딩 속도도 속도지만, 이 부분 때문에 흐린 눈을 뜨고 넘어가기 어려웠다.
그러던 중 전 회사 동료와 이야기하며, Next.js의 Parallel Routes를 알게 되었다.
백 마디 말보다 한 장의 그림이 더 쉽게 설명될 듯 싶다! 말 그대로 병렬 라우팅인데, 하나의 레이아웃 안에서 여러 페이지를 "동시에, 혹은 조건부로" 렌더링 할 수 있는 기능이다.
공식문서에서는 해당 기능을 Modal에도 응용할 수 있다고 친절하게 설명하고 있다.
해당 설명 섹션에 첨부된 이미지 인데, 정확히 내가 고민하고 있던 케이스와 일치했다.
그렇게 Parallel Routes를 적용했더니,
우선 초기 로드 속도가 1.94s로 줄어들었다.
그리고 무엇보다 중요한 것은 스켈레톤 UI를 적용해서 유저에게 데이터가 로드되고 있다는 사실을 분명하게 보여줄 수 있었다. 모달이 등장하는 속도로만 따지면 1.94s보다 훨씬 빠른 속도에 페이지가 전환되는 것이다.
요약하자면, Parallel Routes를 적용하면, 모달을 띄우더라도 SchedulePage가 다시 렌더링되지 않는다.
[AS-IS]의 접근 방식은 다음과 같다.
SchedulePage에서 유저가 원하는 Schedule을 클릭했을 때, 아래와 같은 페이지 이동이 일어난다.
From: ScheduleModal을 품지 않은 SchedulePage
To: ScheduleModal을 품고 있는 SchedulePage
문제는 From에서 To로 이동하면서 SchedulePage가 불필요하게 통째로 다시 렌더링된다는 점이다. 사실 페이지 이동이므로 당연하기도 하지만, 배경이 되고 있는 SchedulePage에 필요한 정보는 이미 다 쥐고 있는데, 이 부분을 다시 렌더링 하지 않으면서 Modal을 띄울 수는 없을까, 고민이 들만도 하다.
SchedulePage와 ScheduleDetailPage를 분리해버리면 사실 쉽게 해결될 문제이다. 그러나, 맨 처음에 기술한 조건들 중 두번째 조건을 이유로 꼭 모달을 통해 ScheduleDetail의 내용을 띄워주고 싶었다.
모달을 띄우면서도, SchedulePage와 ScheduleModal을 별도의 페이지로 접근하는 게 Parallel Routes를 활용하니 가능했다.
[TO-BE], 즉, Parallel Routes의 접근 방식은 하나의 레이아웃에 존재하되, SchedulePage와 ScheduleModal 자체를 별도의 페이지로 분리하는 방식이라고 설명할 수 있겠다. (본인이 이해한 바에 따르면!)
From: ScheduleModalPage가 겹쳐지지 않은 SchedulePage
To: ScheduleModalPage로 이동. SchedulePage가 이미 렌더링 되어있다면, 이 것을 Layout내에 같이 렌더링 함
실제로 Parallel Routes를 적용해보니, 모달을 띄우면서 페이지가 이동할 때, SchedulePage가 다시 렌더링되지 않음을 알 수 있었다. (그리고 위와 같은 접근 방식이다보니, Parallel Routes의 경우, 외부에서 ScheduleModalPage로 바로 접근하면, 배경에 SchedulePage가 레이아웃으로 렌더링 되지 않는다.)
공식문서에 나와있는 문서만 보고 코드를 따라 칠 수는 있지만 왜 그런 모양의 코드가 나오는지 이해하기 어려웠는데, 콘솔로 렌더링 여부를 찍어보고, 관리자 도구를 참고해보니, 이제가 아래 Layout 코드가 왜 이런 모양인지 알 수 있었다.
const StudioSchedulePageLayout = ({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) => {
return (
<>
{children} {modal}
</>
);
};
export default StudioSchedulePageLayout;