Study

[Vue.js] 라우터 미들웨어에서 location.replace() 사용하기 (feat. next())

안자두 2025. 5. 28. 11:56

1. 계기

Vue2 기반 프로젝트에서 로그인 인증 흐름을 구성하던 중, window.location.replace()를 사용해 토큰 노출을 방지하려고 했다. 그러나 예상치 못한 깜빡임과 잘못된 라우팅 문제를 부딪히며 새로운 사실을 알게 되었고, 해결 과정에 재미를 느껴, 이 과정을 포스팅해 보았다.


2. 요구사항 정리

프로젝트 요구사항 플로우를 정리하면 아래와 같다.

  1. 서버 쪽 로그인 페이지에서 로그인
  2. 로그인 검증 후, 성공 시 프론트 쪽 루트 페이지로 리다이렉트
  3. 프론트에 토큰이 없으면, 서버 쪽 검증 페이지로 리다이렉트
  4. 프론트에 토큰이 있으면, 그대로 루트 페이지를 보여줌 (로직 종료)
  5. 검증 성공 후, 서버에서 프론트로 params에 토큰을 담아 루트 페이지로 리다이렉트
  6. params의 토큰이 있으면, 프론트에서 내부에 토큰을 저장 후 사용

 


3. 내가 채택한 방법

요구사항에 추가적으로 아래 사항들을 구현하고 싶었다.

  1. URI에 토큰이 남지 않게 하기
  2. 뒤로 가기를 눌렀을 때 로그인 페이지나 쿼리 포함 URI가 다시 보이지 않게 하기

이 두 가지를 모두 만족시키려면, 페이지가 새로고침되어야 하고, 이전 URI가 히스토리에 남아있으면 안 됐다.
그래서 페이지를 전환시킬 수 있는 방법들을 검색해 비교해 보았다.

방법 히스토리 기록 새로고침 발생 라우터 가드 트리거 여부
window.location.replace(url)
(현재 URL 대체)

(페이지 새로고침)
브라우저 레벨에서 강제 이동
뒤로 가기 방지 가능
location.href = url
(히스토리에 기록됨)

(페이지 새로고침)
뒤로가기 시 URI 노출 가능
$router.replace(path)
(히스토리 교체)

(SPA 내 이동)
쿼리 제거 가능
라우터 흐름 사용 가능
$router.push(path)
(히스토리 추가)

(SPA 내 이동)
뒤로가면 이전 URI 확인 가능
 

내가 원하는 두 가지 조건을 모두 만족시키는 방법은 window.location.replace() !
브라우저 히스토리를 덮어써 URI를 정리할 수 있고, 뒤로 가기를 시도해도 이전 상태를 보여주지 않기 위해서 location.replace()를 선택했다.

 


4. 첫 번째 구현 (실패)

로직을 토대로 아래처럼 구현해 보았다.

router.beforeEach((to, from, next) => {
  if (토큰이 필요한 페이지로 진입 && 토큰이 없을 때) { 
    if (params에서 토큰을 가져올 수 있는 URI일 경우) {
      sessionStorage.setItem('토큰', 토큰); 
      window.location.replace('/');
    } else {
      window.location.replace('https://server.com/login');
    }
  }
  next();
});

 

결과는 실패...(왜???)

머릿속으로 흐름을 생각했을 때는, 제대로 동작할 거라고 생각했는데, 의도치 않게 동작하여 당황하였다.

문제는 프론트에 토큰이 없어서 서버 쪽 검증 페이지로 리다이렉트 되기 전, 프론트 페이지가 잠깐 노출되어, 깜빡이는 것처럼 보였다.


문제 해결을 위해 console을 찍어 페이지 이동될 때마다 확인을 해보았고, 페이지가 전환된 후에, 현재 입력된 URI로 다시 이동하는 것을 확인할 수 있었다.

로직 하단의 next()가 실행되는 것이었다!!


5. 원인 분석

router.beforeEach((to, from, next) => {
  if (토큰이 필요한 페이지로 진입 && 토큰이 없을 때) { 
    if (params에서 토큰을 가져올 수 있는 URI일 경우) {
      sessionStorage.setItem('토큰', 토큰); 
      window.location.replace('/');
    } else {
      window.location.replace('https://server.com/login');
    }
  }
  next(); // 🚨 이 부분에서 문제가 발생
});

window.location.replace()는 브라우저의 페이지 이동 명령을 전달하지만, 자바스크립트의 실행 흐름은 계속 진행된다.

즉, replace() 이후 코드인 next()가 그대로 실행되어 Vue Router는 현재 페이지를 정상적으로 라우팅한다 !!
비동기는 아니지만, 브라우저가 페이지 이동 준비를 하는 동안, 자바스크립트 흐름이 계속 진행되어 next()가 수행된 것이다.

그래서 처음 서버 쪽 검증 페이지로 리다이렉트 되기 전, next() 호출로 프론트 페이지로 라우팅 된 후, 리다이렉트 되었기 때문에 깜빡이는 것처럼 보이게 된 것이었다.

 


6. 두 번째 구현 (문제 해결)

아래처럼 조건 분기를 통해 next() 호출을 통제해 문제를 해결하였다.

router.beforeEach((to, from, next) => {
  if (토큰이 필요한 페이지로 진입 && 토큰이 없을 때) {
    if (params에서 토큰을 가져올 수 있는 URI일 경우) {
      sessionStorage.setItem('토큰', 토큰);
      window.location.replace('/');
    } else {
      window.location.replace('https://server.com/login');
    }
  } else {
    next(); // ✅ 토큰이 있는 경우에만 정상 진행
  }
});

토큰이 필요 없는 페이지이거나, 토큰이 필요한 페이지에 진입할 때 토큰을 갖고 있는 경우에만 라우팅이 정상적으로 실행되도록 분기처리하여 원하는 결과를 얻어낼 수 있었다.


7. 결론

 

SPA 환경에서 브라우저 API와 라우터 가드를 섞어 쓸 때는 흐름 제어가 매우 중요하다. 특히 window.location.replace() 같은 브라우저 레벨 API는 Vue Router의 흐름을 차단하지 않기 때문에 직접 흐름을 분기하고 중단시켜야 한다.

분기처리 하나만으로 결과가 달라져 신기했고, OAuth 인증을 가이드 없이 직접 해본 경험이 처음이라, 이번 기회에 확실히 이해하고 window.location.replace()를 제대로 사용해 본 것 같아 재미있었다. ☆*:.。.꒰ঌ(*´꒳`*)໒꒱.。.:*☆

728x90