[Vue] 새로고침 후 404 에러

🧐

스프링부트와 Vue를 사용한 웹 애플리케이션에서 새로고침, 혹은 URL에 직접 접근할 시 404를 반환하는 에러가 발생했다.

라우터 모드

처음 이 문제를 접하고 가장 먼저 사용했던 방식이다.

라우터를 사용하면 모드를 설정할 수 있는데, hash와 history 두가지 옵션이 있다.

해시 Hash

모든 URL을 해시 형태로 서비스하는 라우터 모드. URL 앞에 #가 붙는다.

해시가 붙은 URL 섹션은 서버로 전송되지 않고 단지 화면 이동에 사용되므로, 따로 서버단에서 처리해줄 필요가 없다.

하지만 SEO에 부적절한 영향을 미친다.

히스토리 모드 Histroy

일반 애플리케이션과 동일하게 정상적인 형태의 URL이 사용된다.

URL이 시각적으로 보기 좋고 검색엔진 최적화에 적합하지만, 페이지 직접 접근시 404를 반환하는 문제가 있다.

 

처음에는 라우터 모드를 해시로 변경해 문제를 해결했고, URL에 붙어있는 #는 애써 외면했다..^^

하지만 추후 카카오 로그인을 도전하게 되면서 보다 근본적인 해결이 필요해보였다.

SPA와 MPA

MVC 패턴을 활용한 프로젝트에서는 보여주고 싶은 페이지를 각각의 html 파일로 작성하고, 요청에 따라 적절한 파일을 반환했다.

이처럼 요청이 들어왔을 때 기존 페이지에서 새로운 페이지로 교체하는 방식을 MPA(Multi Page Application)이라고 한다.

 

하지만 새로운 URL로 이동할때 굳이 html 파일을 완전히 변경할 필요가 있을까?

가령, 헤더나 푸터, 네비게이션 바는 어떤 페이지로 가도 변함없이 존재할테니 굳이 다시 그릴 필요가 없을 것이다.

SPA(Single Page Application)에서는 하나의 페이지만을 사용하고 URL에 따라 특정 부분만 동적으로 변경한다.

index.html

Vue는 SPA를 위한 프레임워크이다.

라우터를 통해 URL을 이동하는데, 여기서 발생하는 화면 변경은 index.html이라는 하나의 파일에서 이루어진다.

 

다시 한번 에러 발생 상황을 살펴보자.

루트 경로에서 새로고침을 했을 때에는 정상적으로 새로고침이 이루어지지만, 상세 페이지에서 새로고침하면 화이트라벨로 이동했다.

잉? 화이트라벨 페이지는 스프링부트 프로젝트를 최초로 실행했을 때 나타나는 화면이 아닌가.

그렇다면 단순하게 API에서 요청을 보낸다고 생각해보자.

(1) 루트 경로 요청

루트 경로 /로 요청을 보내면 기본적으로 index.html을 반환한다.

(2) REST GET 요청

/movie/65라는 경로로 요청을 보내면 65번 영화의 JSON 데이터를 반환한다.

(3) 존재하지 않는 요청

만약 /content/movie/64로 요청을 보내면?

핸들러매핑에 일치하는 메서드가 없으니 404를 리턴한다.

 

문제를 찾았다 🥹

나는 브라우저에 URL을 입력하면서 프론트단의 특정 페이지로 이동할 것을 기대했지만, 실제로 요청은 백엔드 즉 API로 보내진 것이다.

 

브라우저에서 루트 경로로 접속하면 API의 루트 응답을 통해 index.html 파일을 반환받고, 이 유일한 페이지에서 모든 화면이 그려진다.

하지만 루트가 아닌 경로에서 접근하면 API에 해당 URI로 된 요청이 없거나, 설령 있더라도 JSON 데이터일 뿐 index.html 파일이 없으니 화면을 그릴 수 없는 것이다.

ErrorController

그렇다면 방법을 찾았다.

404가 발생했을 때, 에러페이지(화이트라벨)가 아니라 index.html로 이동하는 것이다.

 

요청을 받거나 처리하는 과정에서 발생하는 에러들을 일괄적으로 관리하는 데에 사용되는 ErrorController를 통해 이러한 로직을 구현해보자.

@Controller
public class ErrorController implements org.springframework.boot.web.servlet.error.ErrorController {
    private final String ERROR_PATH = "/error";

    @GetMapping(ERROR_PATH)
    public String redirectRoot(HttpServletResponse response) {
        response.setStatus(HttpStatus.OK.value());
        return "index.html";
    }
}

 

에러가 발생했을 때 index.html 파일을 반환해주었다.

브라우저 콘솔에 404 발생 여부를 알려주지 않기 위해 상태코드를 200으로 변경했다.

이제 새로고침을 하거나 URL에 직접 접근하더라도 에러가 발생하지 않는다.