본문 바로가기

TIL

일정등록 모달 구현 방식 개선

배경

처음 기능을 구현할 때는 모바일 퍼스트 접근으로 달력의 일정을 클릭하면 일정을 등록하고 관리하는 화면을 모달이 아닌 날짜 값을 받는 동적 경로로 페이지를 구현했었다. 그러나 웹 디자인 시안을 검토한 결과, 일정 관리 페이지가 모달 형태로 설계되어 있었다.

이에 따라, 같은 페이지 내에서 다른 경로의 URL을 어떻게 효과적으로 보여줄 수 있을지 고민하게 되었다.



 일정 클릭 시
 

/study/[studyId]/[date]
새로운 페이지로 이동

 

 

 

 

■ 초기 접근

Next.js의 Parallel Routes 또는 Intercepting Routes 기능을 활용하면 기존 페이지를 유지하면서 모달 형태로 일정 관리 화면을 구현할 수 있을 것이라고 판단했다. 이를 공부하고 적용해 보았으나, 뷰에 맞게 분기 처리하는 것이 쉽지 않았고, 컴포넌트 구조가 복잡해지는 문제가 발생했다.

 

■ parellel routes와 Intercepting Routes 알아보기

 

parellel routes (병렬 경로)

같은 레이아웃 에서 하나 이상의 페이지를 동시에 또는 조건부로 렌더링할 수 있는 기능

slots 에 의해 생성이 되며, 생성된 slot 속 페이지는 slot과 같은 레벨의 레이아웃에 props 로 전달된다.

- slot 폴더는 url 에 영향을 주지 않고 무시된다.

-  대시보드나 소셜 사이트의 피드, 모달 등을 구현할때 유용하다.

- 복잡한 레이아웃을 깔끔하게 구현할 수 있으며 서로 독립적인 로딩/에러 상태 처리가 가능하다.

- SEO 친화적인 모달을 구현할 수 있다.

 

* slot

- 병렬 경로는 slot 을 사용해 생성된다.

- @folder 로 만든 폴더, 이 폴더의 내용을 layout 에서 prop으로 받아서 원하는 위치에 보여줄 수 있다.

- slot 을 사용하면 여러 페이지를 동시에 보여주고 독립적으로 관리할 수 있다.


@analytics → slot



@team → slot


 

 

Intercepting Routes

현재 페이지의 컨텍스트를 유지하면서 다른 라우트를 보여주는 기능

- 주로 모달이나 슬라이드 오버 구현에 사용

- URL 을 통한 직접 접근도 가능해 SEO 친화적이다.

 

 

convention

(.) - 동일한 레벨
(..) - 한 단계 뒤로
(..)(..) - 두 단계 뒤로
(...) - 루트 app 디렉토리

 

 

parellel routes vs Intercepting Routes 차이

parellel routes Intercepting Routes
- 여러 콘텐츠를 나란히 보여줌
- 독립적인 섹션 구성
- 레이아웃 분할에 용이
- 현재 페이지 위에 새 콘텐츠 표시
- 모달, 오버레이 UI에 적합
- 컨텍스트 유지하면서 새 내용 표시

 

나의 시나리오

모바일 : 날짜 클릭 → 새 페이지로 이동

데스크탑: 날짜 클릭 → 같은 페이지에서 모달로 표시

 

URL 구조

/study/[id] -> 메인 캘린더 페이지

/study/[id]/[date] -> 날짜별 일정 페이지

 

 

■ 최종 구현 방식

각각 다른 뷰로 컴포넌트를 생성 후 분기 처리하거나 쿼리스트링을 사용하는 피드백을 받았고, 하나의 컴포넌트로 분기 처리하기 위해 쿼리스트링으로 searchParams를 생성하는 방식을 선택했다.

Before: /study/[id]/[date] 
After: /study/[id]?date=&modal=calendar

 

 

■ 발생한 문제 및 해결

🚨 ① 뒤로가기 로직 문제

모달이 띄워진 후 뒤로가기를 두 번 해야 원래 페이지로 돌아가는 문제가 발생했다.

 

시도한 방법

- 쿼리스트링으로 보내주는 로직에 router.push 가 아닌 router.replace 로 대체해봤지만 뒤로가기 자체가 실행되지 않았다.

  // GroupCalendar.tsx
  const handleDateClick = (selectedDate: Date | undefined) => {
    if (selectedDate) {
      const formattedDate = format(selectedDate, "yyyy-MM-dd");
      setDate(selectedDate);

      const params = new URLSearchParams(searchParams);
      params.set("date", formattedDate);
      params.set("modal", "calendar");
      router.push(`?${params.toString()}`);
    }
  };

 

- 그래서 usePathname을 사용해 모달인 경우 쿼리스트링을 제거하고, 아닌 경우 뒤로가기를 하도록 조건 처리해주었다.

// 모달인 경우 쿼리스트링 제거, 아니면 뒤로가기
    if (searchParams.get("modal") === "calendar") {
      router.replace(pathname);
      // 히스토리 스택 정리
      window.history.pushState(null, "", pathname);
      window.history.replaceState(null, "", pathname);
    } else {
      router.back();
    }

 

🟡 이후 모바일과 데스크탑 디자인의 통일성을 주기위해 모바일에만 존재했던 뒤로가기 버튼이 데스크탑과 동일하게 닫기로 변경되었다.

      (일정관리 페이지에서의 뒤로가기 버튼)

 

🚨 ② 스크롤 위치 유지 문제

뒤로가기가 아닌 닫기로 로직을 수정한 후 테스트를 해보니 쿼리스트링도 제거되고 모달도 꺼지지만 스터디 룸 페이지에서 뒤로가기 시 이전에 스크롤한 위치로 이동하고 한번 더 뒤로가기를 눌러야 이전페이지로 이동되는 문제가 발생했다.

 

 해결  

studyId가 있을 경우 그룹 페이지의 이전 페이지인 /study로 이동하도록 분기 처리하였다.

  const handleClick = () => {
    if (studyId) {
      router.push("/study");
    } else {
      router.back();
    }
  };

 

 

■ 향후 보완사항

현재 구현은 웹 시안에 뒤로가기 버튼이 없어 문제가 발생하지 않을 것으로 예상된다. 브라우저의 기본 뒤로가기 버튼을 사용할 경우를 테스트 해보니 뒤로가기를 눌러도 모달이 켜지지는 않지만 두번눌러야 페이지가 이동하는 문제가 확인되었다. 

모달이 닫힐 때 히스토리를 조정하는 로직을 추가하거나 다른 정확한 방법에 대해 고민이 필요할 듯 하다.

 

 

🧐 느낀점

이번 경험을 통해 Next.js의 라우팅 시스템과 모달 구현에 대해 좀 더 깊이있게 공부할 수 있었고 특히 Parallel Routes와 Intercepting Routes에 대해 공부하면서, 복잡한 UI 패턴을 구현하는 다양한 방법이 있다는 것을 알게 되었다.

또한, 모바일과 데스크탑 환경에서의 일관된 사용자 경험을 제공하기 위해 노력하면서, 반응형 디자인의 중요성을 다시 한 번 깨달았다.

앞으로도 사용자 중심의 설계와 구현을 위해 좀 더 신경써서 노력해야겠다는 생각이 들었다.