Next.js 라우팅 트러블슈팅: URL vs 로컬 스토리지
VS Code 스타일의 UI를 웹으로 구현하면서, **"이전에 열어둔 탭을 기억하는 기능"**은 필수적이었습니다. 이를 위해localStorage를 사용하여 히스토리 상태를 관리했는데, 이 과정에서 예상치 못한 라우팅 문제가 발생했습니다.오늘은 딥링킹(Deep Linking)이 불가능했던 이슈와 이를 해결해나가는 과정을 정리해보려 합니다.1. 기본 플로우 (문제점)
상황
사용자가 사이트를 재방문했을 때 이전 작업 환경을 그대로 보여주기 위해,HistoryContext를 만들어 로컬 스토리지에 열려있는 탭 목록과 활성 탭 정보를 저장했습니다.문제 발생
"첫 유저를 어디론가 랜딩 시켰을 때" 문제가 터졌습니다. 예를 들어, 친구에게 "나 게임 만들었어! 해봐"라며https://vscoke.icecoke.kr/game 링크를 공유했다고 가정해봅시다.- 사용자는
/game으로 접속합니다. - 하지만 앱이 켜지는 순간,
HistoryTabs컴포넌트가 로컬 스토리지를 확인합니다. - "어? 저장된 히스토리가 없네? (또는 마지막에 본 게 홈이네?)"
- "메인으로 가!" ->
/로 강제 리다이렉트.
Hello, I'm Icecoke!)만 보게 되는 치명적인 이슈가 있었습니다.2. 고민했던 방법들
이 문제를 해결하기 위해 몇 가지 방법을 고민했습니다.방법 A: 예외 처리 추가
- 히스토리가 비어있을 때는 리다이렉트를 하지 않는다?
- 하지만 만약 히스토리가 있으면? 사용자가
/game으로 들어왔는데, 저장된 기록에 따라/About으로 보내버린다면 더 이상하겠죠.
방법 B: URL 우선권 부여
- 로컬 스토리지 데이터보다 현재 접속한 URL을 더 높은 우선순위로 둔다.
- "기억된 것"보다 "지금 사용자가 보러 온 것"이 더 중요하다는 접근입니다.
3. 해결 시도와 사이드 이펙트
1차 시도: Source of Truth 변경
기존에는HistoryTabs가 주도권을 가지고 라우터를 조종했습니다.
"히스토리 탭이 바뀌면 -> 라우터도 이동해"이것을 반대로 뒤집었습니다.
"URL이 바뀌면 -> 히스토리 탭을 맞춰"// 변경 전 (HistoryTabs가 대장)
useEffect(() => {
if (current) router.replace(current.path);
}, [current]);
// 변경 후 (URL이 대장)
const pathname = usePathname();
useEffect(() => {
// URL에 맞는 탭이 없으면 추가, 있으면 활성화
syncTabWithUrl(pathname);
}, [pathname]);발생한 사이드 이펙트 (Side Effect)
이렇게 바꾸고 났더니, 갑자기 사이드바(탐색기)가 고장 났습니다. 파일을 클릭해도 URL은 안 바뀌고, 탭만 번쩍거리는 기이한 현상이 발생했죠.원인은 사이드바 아이템(ExplorerItem)에 있었습니다.
예전에는 사이드바가 history.add()만 호출하면, HistoryTabs가 그걸 감지해서 router.push를 대신 해줬던 구조였기 때문입니다.HistoryTabs의 router 제어권을 뺏으니, 아무도 주소를 바꿔주지 않게 된 것입니다.4. 최종 적용된 방법
결국 **"URL을 모든 상태의 중심(Single Source of Truth)으로 둔다"**는 원칙을 확고히 했습니다.- 사이드바 수정: 아이템 클릭 시 상태를 건드리지 않고, 무조건
router.push(url)만 수행합니다. Next.js의 방식 그대로 따르는 것이죠. - HistoryTabs 수정:
router가 변경되면(URL이 변하면) 그때useEffect가 돌면서 **"어? 새 주소네? 탭 열어줄게"**라고 반응형으로 동작합니다. - Hydration 처리: 서버 사이드 렌더링(SSR)과 로컬 스토리지 간의 불일치를 막기 위해,
isHydrated체크를 넣어 브라우저 로딩이 끝난 후에만 동기화 로직이 돌도록 안전장치를 걸었습니다.
// 최종 로직 흐름
User Click -> router.push('/game') -> URL Change -> HistoryTabs useEffect -> Add Tab & Active