SpringBoot 서버만 실행시켰을 때 React로 작성된 웹 앱이 같이 동작되게 할려면?
보통 SpringBoot 내에서 정적리소스를 Serving한다고 하면
브라우저 요청 → DispatcherServlet → Controller → ViewResolver(Thymeleaf) → HTML 렌더링 → 응답
위 과정을 거치게 된다.
1. DispatcherServlet이 요청을 받음
Spring Boot 내장 톰캣 서버는 클라이언트의 HTTP 요청을 DispatcherServlet으로 전달
이 서블릿은 Spring MVC의 핵심이며 요청을 적절한 컨트롤러로 라우팅
2. 컨트롤러에서 데이터와 뷰 이름 반환
@GetMapping("/home")
public String home(Model model) {
model.addAttribute("msg", "Hello!");
return "home"; // "home.html" 템플릿을 의미
}
Model
에 데이터를 담고"home"
이라는 이름의 뷰(View)를 반환합니다.3. ViewResolver가 템플릿 위치와 확장자 지정
ThymeleafViewResolver
가 아래 설정을 기반으로 뷰 파일 경로를 찾음
spring:
thymeleaf:
prefix: classpath:/templates/
suffix: .html
"home" → classpath:/templates/home.html
4. Thymeleaf Template Engine이 HTML 처리
템플릿 안의 th:* 속성을 파싱해, Model에 있는 값으로 동적으로 치환합니다.
<h1 th:text="${msg}">기본 텍스트</h1>
msg = "Hello!" 이므로 실제 HTML은
<h1>Hello!</h1>
5. 최종 HTML을 응답
렌더링된 HTML이 DispatcherServlet
을 통해 다시 클라이언트로 전송
구성 요소 | 역할 |
---|---|
DispatcherServlet | 요청을 Controller로 전달하고 응답을 다시 반환 |
Controller | 데이터 처리 후 View 이름 반환 |
Model | View에 전달할 데이터 |
ThymeleafViewResolver | View 이름 → 실제 템플릿 파일 매핑 |
Thymeleaf Template Engine | HTML 템플릿 렌더링 |
templates/ 디렉토리 | 템플릿 파일이 위치하는 곳 |
만약 SPA(React,Vue)로 구성된 정적파일을 서빙한다고 하면?
pring Boot는 정적 파일을 서빙하는 서버 역할만 하게 되며, 다음과 같은 흐름으로 이루어 진다.
브라우저 요청 → Spring Boot 정적 리소스 핸들러 → index.html → React Router가 이후 라우팅 처리
1. React 빌드
Vite로 빌드를 해서 Build 설정에 따라 빌드 결과물(정적파일들)이 나오게 된다.
build: {
// remove previous build files
emptyOutDir: true,
// static assets directory
assetsDir: "assets",
minify: "terser",
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
cssCodeSplit: true,
target: "es2019",
rollupOptions: {
output: {
entryFileNames: "[name].js",
assetFileNames: "[name].[ext]",
chunkFileNames: "[name].js",
manualChunks: {
react: ["react", "react-dom"],
reactRouter: ["react-router-dom"],
chartJs: ["chart.js"],
},
},
},
},
2. Spring Boot와 통합 구조
React build 결과를
Spring Boot의 resources/static 혹은 resources/public 아래로 복사
src/
└── main/
└── resources/
└── static/
├── index.html
└── static/
├── js/
└── css/
3. React에서 라우팅 처리
React에서 react-router-dom을 쓰고 있다면, 사용자가 /about, /dashboard 같은 SPA 경로로 접근
그러므로 Spring Boot가 모든 경로를 index.html로 포워딩하도록 설정이 필요
Controller Forward
@Controller
public class SpaController {
@GetMapping(value = { "/", "/{path:^(?!api).*}" })
public String forward() {
return "forward:/index.html";
}
}
일반적인 구조
ViewTemplate를 사용하는 경우
Routing
요청: /k6mt-surfer/dashboard
↓
addResourceHandlers → 파일 존재 여부 확인
↓
없으면 fallback → index.html 반환
↓
React 앱이 내부 라우팅으로 처리 (SPA)
ViewTemplate Engine(정적리소스)를 사용할 때, 관련 URL이 한가지 정적리소스로 forwarding
1. @Configuration
+ WebMvcConfigurer
WebMvcAutoConfiguration
이후에2. addViewControllers → 기본 진입점 리다이렉트
registry.addViewController("/k6mt-surfer").setViewName("forward:/k6mt-surfer/index.html");
registry.addViewController("/k6mt-surfer/").setViewName("forward:/k6mt-surfer/index.html");
3. addResourceHandlers → 정적 리소스 매핑
registry.addResourceHandler("/k6mt-surfer/**")
.addResourceLocations("classpath:/META-INF/resources/k6mt-surfer/")
classpath:/META-INF/resources/k6mt-surfer/
내부에 있는
정적 파일(html/js/css 등)을 /k6mt-surfer/**
경로에 매핑
4. .resourceChain().addResolver()
→ SPA 라우팅 처리
@Override
protected Resource getResource(String resourcePath, Resource location) throws IOException {
Resource requested = location.createRelative(resourcePath);
return (requested.exists() && requested.isReadable())
? requested
: location.createRelative("index.html"); // fallback
}
요청: /k6mt-surfer/dashboard
실제 파일 존재 ❌ → index.html 반환 → React Router가 페이지 렌더링
SPA 앱들은 /#/, /dashboard, /settings 등의 경로를 내부적으로 처리하지만
서버는 이 경로를 알지 못함 → 정적 파일이 아니면 404 발생
그래서 다음이 필요함 ⏩
(addResourceHandler)
(PathResourceResolver)
(addViewControllers)