router.replace()가 미들웨어를 건너뛰는 문제
로그인에 성공했는데도 router.replace()가 먹히지 않아 이전 "접근 불가" 상태가 그대로 남았다.
원인은 Next.js 라우터 캐시 — 미들웨어가 언제 건너뛰어지는지부터 다시 파야 했다.
핵심 요약
Next.js 공식 문서와 실제 동작에 기반한 결론은 다음과 같다.
router.replace()는 라우터 캐시가 있을 때만 서버 요청을 생략한다.useRouter()로 방문한 페이지에만 라우터 캐시가 생성된다.- 인증 상태가 바뀌어도 라우터 캐시는 자동으로 갱신되지 않는다.
- 미들웨어는 서버 요청이 있을 때만 실행된다.
그래서 인증 상태 변경 후에는 window.location.href로 전체 페이지를 새로고침하는 게 가장 확실하다.
❶ 라우터 캐시와 미들웨어의 기본 원리
라우터 캐시란?
useRouter()를 통한 클라이언트 측 탐색 결과를 저장하는 메커니즘- 페이지 데이터·렌더링 결과·라우팅 결정을 포함
- 기본적으로 브라우저 세션 동안 유지됨
미들웨어란?
- 서버 요청이 발생할 때, 페이지가 렌더링되기 전에 실행되는 코드
- 인증 상태 확인·리다이렉션·헤더 수정 등을 수행
- 중요: 서버 요청이 있을 때만 실행된다.
❷ 시나리오별 동작
- 첫 방문 (캐시 없음) — URL 직접 입력/링크 클릭. 서버 요청 → 미들웨어 실행 → 권한 확인. 아직 라우터 캐시 없음.
- useRouter() 첫 접근 —
push/replace로 첫 접근. 캐시가 없으니 서버 요청 → 미들웨어 실행. 이때 해당 경로의 라우터 캐시가 생성되고, 미들웨어의 리다이렉션 결정("접근 불가")도 함께 캐시에 저장된다. - useRouter() 재접근 — 캐시가 있으니 서버 요청 생략 → 미들웨어도 실행 안 됨. 인증이 바뀌어도 캐시는 자동으로 갱신되지 않는다.
- 방문했지만 useRouter()로 접근하지 않은 페이지 — 주소창 입력/
<a>링크로 방문한 페이지는 캐시가 생기지 않는다. 이후replace()로 접근하면 캐시가 없어 서버 요청 → 미들웨어 실행.
❸ 흔한 오해 바로잡기
오해 1 — "router.replace()는 항상 미들웨어를 안 거친다"
→ 사실: 라우터 캐시가 있을 때만 서버 요청을 생략한다. 캐시가 없으면 replace()도 미들웨어를 거친다.
오해 2 — "이미 방문한 페이지는 항상 캐시가 있다"
→ 사실: useRouter()(push/replace)로 방문한 페이지만 캐시가 생긴다. 주소창 입력·<a> 링크 방문은 캐시가 생기지 않는다.
❹ 실제 사례
사례 1 — 환자 로그인·등록
- 로그인 후
router.replace('/patient')→ 캐시 없음 → 서버 요청 → 미들웨어가name이 null임을 확인 →/patient/admission으로 리다이렉트. "/patient접근 불가" 결정이 캐시에 저장됨. - 환자 등록 후 다시
router.replace('/patient')→ 캐시 있음 → 서버 요청 생략 → 미들웨어 미실행 → 캐시된 "접근 불가"로 여전히 막힘. - 해결:
window.location.href = '/patient'로 강제 새로고침.
사례 2 — 관리자 로그인/로그아웃
- 로그인 상태로
/admin주소창 접근 → 캐시 없음. /admin에서router.replace('/admin/login')→ 캐시 없음 → 서버 요청 → 미들웨어가 "이미 로그인" 확인 →/admin으로 리다이렉트. "/admin/login접근 불가"가 캐시에 저장됨.- 백엔드 로그아웃 후 다시
router.replace('/admin/login')→ 캐시 있음 → 미들웨어 미실행 → 여전히 막힘. (쿠키가 삭제돼도 서버액션과 무관해 캐시는 갱신되지 않는다.)
❺ 라우터 캐시 무효화 방법
클라이언트 측
// 전체 페이지 새로고침 — 가장 확실
window.location.href = "/admin/login";
// router.refresh()는 현재 URL 캐시만 갱신 (대상 경로 캐시는 그대로)
router.refresh();
router.replace("/admin/login"); // 이 경로 캐시는 여전히 존재document.cookie로 쿠키를 지워도 라우터 캐시는 무효화되지 않으며, HttpOnly 쿠키는 JS로 접근조차 안 된다.
서버 측 (서버액션)
revalidatePath("/admin/login"); // 특정 경로 캐시 무효화
// cookies.set/delete 시 관련 경로의 라우터 캐시가 자동 무효화됨
cookies.set/delete가 라우터 캐시를 무효화한다.❻ 권장 구현
인증 상태가 바뀌는 동작(로그인/로그아웃/환자등록) 후에는 라우터 캐시를 무시하고 전체 새로고침한다.
const handleLogout = async () => {
await mutateAsync({}); // 백엔드에서 쿠키 삭제
// 라우터 캐시를 무시하고 새 서버 요청 생성
window.location.href = "/admin/login";
};배운 점
증상이 "특정 흐름(미들웨어 경유)에서만" 재현되면 로직보다 캐시를 먼저 의심한다. 프레임워크의 캐시 계층을 이해하면 "분명 바꿨는데 안 바뀌는" 버그를 구조적으로 설명하고 재발을 막을 수 있다.