spring boot + vue project router문제와 404 해결
이전글 : https://cronex.tistory.com/23
spring-boot 와 vue 이용하여 프로젝트 생성하기
spring boot 와 vue를 이용하여 프로젝트 생성하기 프로젝트를 시작하게 된 계기는 김영한님의 JPA 강의를 들으면서 vue도 다시 공부할 겸, thymeleaf 말고 vue를 이용하여 개발을 해보자 하여 시작하게
cronex.tistory.com
프로젝트를 생성 후 front-end에서 build한 결과물을 back-end 내 resources 하위에 static 폴더와 함께 생성하는 구조까지 만들었었습니다. 이 후 vue에 vue-router를 추가하면서 문제가 발생하였는데, build 후 spring-boot로 서버를 띄웠을 때 최초 localhost:8080/ [index.html]으로의 요청은 문제가 되지 않았습니다.
vue-router 설정은 아래와 같습니다.
import { createRouter, createWebHistory } from "vue-router";
import WelcomeHome from "@/components/WelcomeHome";
import CoachList from "@/components/CoachList";
import TeamList from "@/components/TeamList";
const router = createRouter({
history: createWebHistory(),
routes: [
{path: "/", component: WelcomeHome},
{path: "/team", component: TeamList},
{path: "/coach", component: CoachList},
],
});
export default router;
App.vue
App.vue
<template>
<p>WelComeHome</p>
<router-link to="/team">team으로 이동</router-link>
<router-link to="/coach">Coach로 이동</router-link>
</template>
<script>
export default {
name: "WelcomeHome"
}
</script>
<style scoped>
</style>
App.vue에 작성한대로 vue-router를 이용한 페이지 이동은 문제가 되지 않았습니다.
vue-router를 이용한 /team으로의 이동
TeamList.vue
<template>
<p>Team List comp</p>
</template>
<script>
export default {
name: "TeamList"
}
</script>
<style scoped>
</style>
vue-router를 이용한 /coach 페이지 이동
CoachList.vue
<template>
<p>Coach List</p>
</template>
<script>
export default {
name: "CoachList"
}
</script>
<style scoped>
</style>
문제
문제는 이 이후 발생했습니다..브라우저 URL로 http://localhost:8080/coach 또는 vue-router로 이동한 coach 페이지에서 새로고침을 했을 때 아래와 같은 404 error page를 만나게 되었습니다... [ vue-router의 history value를 createWebHistory()로 설정함 ]
404 페이지가 발생한 이유를 곰곰히 생각해보니, index.html 파일 내에서의 link를 통한 페이지 이동은 vue-router를 통해 이동하기 때문에 발생하지 않았지만, http://locahost:8080/coach 이나 새로고침 같은 경우 vue-router를 통한 화면 전환이 아닌, server에게 요청을 보내고 해당 결과를 보여주려 하고 있었기 때문에 404 페이지를 만나게 된 것이었습니다.
즉, back-end server에는 /coach 에 해당하는 resourecs 나 controller가 작성되어있지 않았기 때문에 server에서는 404 page를 뱉은것이었습니다.
다시 브라우저에서 network 탭을 열어서 확인해보면 localhost:8080 요청 후 vue-router로 team으로 이동할 때 network탭을 보면 request를 보내지 않는다는 것을 확인 할 수 있습니다.
하지만 문제의 새로고침 이나 localhost:8080/team 을 브라우저에 직접 입력하게 되면 아래처럼 server에 request를 보내게 된 것입니다.
해결방법부터 말씀드리자면 component-scan 범위에 해당하는 package에 WebMvcConfigurer를 구현한 class를 추가해주면 됩니다.
package jpaBook.jpaShop.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.PathResourceResolver;
import java.io.IOException;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/**")
.resourceChain(true)
.addResolver(new PathResourceResolver() {
@Override
protected Resource getResource(String resourcePath, Resource location) throws IOException {
Resource resource = location.createRelative(resourcePath);
return resource.exists() && resource.isReadable() ? resource : new ClassPathResource("/static/index.html");
// return super.getResource(resourcePath, location);
}
});
// WebMvcConfigurer.super.addResourceHandlers(registry);
}
}
현재 프로젝트의 resources 구조는 아래와 같습니다.
프로젝트 구조를 보면 아시겠지만, static 하위에 build 한 결과물이 생기기 때문에 addResourceLocations에 인자 값으로 classpath:/static/** 를 주었습니다. 만약 저와 프로젝트 구조가 다르다면 그에 맞게 수정하시면 될 듯 합니다.
수정 후 localhost:8080/team 으로 요청을 보내거나 새로고침을 하고 난 후 network 탭을 보면
localhost:8080/team 으로 요청을 보내고 응답이 온 것을 확인할 수 있으면 reuqest의 response 탭을 보면 index.html 파일이 repsonse에 담겨있는것을 볼 수 있습니다.
<!doctype html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" href="/favicon.ico">
<title>front-end</title>
<script defer="defer" src="/js/chunk-vendors.5d66e1d9.js"></script>
<script defer="defer" src="/js/app.c310bfbf.js"></script>
<link href="/css/app.b2d625b8.css" rel="stylesheet">
</head>
<body>
<noscript><strong>We're sorry but front-end doesn't work properly without JavaScript enabled. Please enable it to
continue.</strong></noscript>
<div id="app"></div>
</body>
</html>
동작이 가능한 이유를 생각해보면
1. localhost:8080/team 으로 요청을 보내면 server에서는 설정한 값에 의해 index.html을 내려주게 된다.
2. 브라우저는 요청을 받아 index.html 을 읽으면서 index.html 파일에 있는 script와 css 요청을 서버에 다시 보낸다
3. 해당 js, css 파일을 받아 렌더링하면서 vue-router에 의해 routes로 설정한 /team page로 이동하게 되었다.
라고 할 수 있습니다.
마지막으로 vue-router에 매칭되지 않은 path 요청을 처리하기 위해 notFound path를 매핑해주었습니다.
import { createRouter, createWebHistory } from "vue-router";
import WelcomeHome from "@/components/WelcomeHome";
import CoachList from "@/components/CoachList";
import TeamList from "@/components/TeamList";
const router = createRouter({
history: createWebHistory(),
routes: [
{path: "/", component: WelcomeHome},
{path: "/team", component: TeamList},
{path: "/coach", component: CoachList},
{path: "/:notFound(.*)", redirect: "/"}
});
export default router;
/notFound(.*) path를 최하위로 두어 위에 설정한 route 값에 매칭되는 것이 없을경우 /notFound(.*) path를 타게되어 index 페이지로 리다이렉트 시켜주었습니다.
/notFound(.*) 를 잘 모르시는 분들은 아래 링크를 참고하시면 될 것 같습니다.
- https://router.vuejs.org/guide/essentials/route-matching-syntax.html
Vue Router | The official Router for Vue.js
The official Router for Vue.js
router.vuejs.org
이제 locahost:8080/asudasbd 와 같은 매칭되지 않은 path 요청을 보낼경우,
서버에서는 index.html 파일을 내려주고,
vue-router에 의해 index 페이지로 리다이렉트 시키는 것을 볼 수 있습니다.
출처: https://stackoverflow.com/questions/38516667/springboot-angular2-how-to-handle-html5-urls