Vue와 Spring Boot 연동 구조: 개발 프록시부터 서빙 전략까지
한눈에 보기
Vue SPA와 Spring Boot API 서버를 분리해 HTTP와 JSON으로만 통신하는 구조를 다룬다. Vite 프록시와 CORS 처리, 빌드 산출물 서빙 전략, 히스토리 모드 새로고침 404 해결을 원리 중심으로 설명한다.
최종 업데이트 2026.09.17
Vue(뷰)와 Spring Boot(스프링 부트)를 연동한다는 것은 화면 렌더링을 담당하는 Vue 싱글 페이지 애플리케이션(Single Page Application)과 비즈니스 로직을 담당하는 Spring Boot API 서버를 분리해 구성하고, 둘 사이를 HTTP와 JSON 통신으로만 잇는 구조를 만드는 일이다. 개발 환경에서는 Vite(비트) 개발 서버의 프록시(proxy)와 CORS(Cross-Origin Resource Sharing) 처리가, 배포 환경에서는 빌드 산출물의 서빙 위치 결정이 연동의 핵심이 된다. 이 문서는 설치 절차가 아니라 이러한 구조와 원리를 중심으로 설명한다.
분리 아키텍처의 기본 구조
서버 템플릿 기반 구조에서는 화면 전환마다 서버가 새 HTML을 렌더링하지만, 분리 구조에서는 Spring Boot가 HTML을 전혀 만들지 않는다. 최초에 정적 파일 한 벌이 브라우저에 전달된 뒤에는 Vue Router(뷰 라우터)가 화면 전환을 담당하고, 데이터가 필요할 때만 API를 호출해 JSON을 받는다. 두 애플리케이션의 접점이 API 명세 하나로 좁혀지므로 프론트엔드와 백엔드를 서로 다른 언어, 저장소, 배포 주기로 운영할 수 있고, 같은 API를 모바일 클라이언트가 재사용할 수도 있다.
개발 환경: Vite 개발 서버와 프록시
개발 중에는 Vite 개발 서버(기본 5173 포트)와 Spring Boot(기본 8080 포트)가 동시에 실행된다. 브라우저의 동일 출처 정책(same-origin policy) 때문에 5173에서 로드된 페이지가 8080의 API를 직접 호출하면 응답이 차단된다. 가장 간편한 우회는 Vite의 프록시 기능이다.
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
});
이렇게 설정하면 프론트엔드 코드는 /api로 시작하는 상대 경로로만 호출하고, 개발 서버가 그 요청을 백엔드로 대신 전달한다. 브라우저 입장에서는 같은 출처와 통신하는 것이므로 CORS가 아예 발생하지 않는다. 참고로 Vite에서는 VITE_ 접두사가 붙은 환경 변수만 클라이언트 코드에 노출되며, 노출된 값은 누구나 볼 수 있으므로 비밀 값을 담으면 안 된다.
CORS를 백엔드에서 허용하는 방법
프록시 없이 브라우저가 다른 출처의 API를 직접 호출하게 하려면, Spring Boot가 응답 헤더로 허용할 출처와 메서드를 알려 주어야 한다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://app.example.com")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE")
.allowedHeaders("*");
}
}
단순 요청의 조건을 벗어나는 요청, 예를 들어 JSON 본문을 담은 PUT 요청 전에는 브라우저가 OPTIONS 메서드의 프리플라이트(preflight) 요청을 먼저 보내 허용 여부를 확인한다. Spring Security를 함께 쓴다면 시큐리티 필터 체인에도 CORS 설정을 등록해야 프리플라이트가 인증 필터에 가로막히지 않는다.
빌드 산출물 서빙 전략
빌드가 만들어 내는 dist 디렉터리는 어떤 서버로도 서빙할 수 있는 순수 정적 파일이다. 서빙 주체에 따라 전략이 갈린다.
| 구분 | 단일 배포 | 분리 배포 |
|---|---|---|
| 구성 | dist 내용을 Spring Boot 정적 리소스로 포함해 jar 하나로 배포한다 | 정적 파일은 Nginx나 CDN이 서빙하고 API 서버는 따로 둔다 |
| 운영 난이도 | 배포 대상이 하나라 단순하다 | 구성 요소가 늘어나지만 역할이 명확하다 |
| 배포 독립성 | 화면만 고쳐도 전체를 다시 배포한다 | 프론트엔드만 즉시 교체할 수 있다 |
| 캐싱과 성능 | 애플리케이션 서버가 정적 요청까지 처리한다 | CDN 캐싱으로 전 세계 응답 속도를 높일 수 있다 |
히스토리 모드 라우팅과 새로고침 404
Vue Router의 createWebHistory 모드는 /orders/7처럼 실제 경로 형태의 URL을 만든다. 이 URL은 서버의 파일 시스템에 존재하지 않으므로 새로고침하거나 링크로 직접 진입하면 404가 발생한다. 해결책은 서버가 매칭되지 않는 경로를 index.html로 되돌려 주는 폴백(fallback) 설정이며, 이후 라우팅은 브라우저에서 Vue Router가 이어받는다. 해시 모드(createWebHashHistory)를 쓰면 서버 설정 없이 동작하지만 URL에 # 기호가 남아 공유와 검색 엔진 최적화에 불리하다. 폴백을 구성할 때 API 경로는 반드시 제외해서, 잘못된 API 호출이 HTML을 응답받는 상황을 막아야 한다.
면접에서 이렇게 물어본다
- Vite 개발 서버의 프록시가 CORS 문제를 우회할 수 있는 이유를 동일 출처 정책과 연결해 설명해 보라.
- 프리플라이트 요청은 어떤 조건에서 발생하며 Spring에서 어떻게 대응하는가.
- createWebHistory 모드에서 새로고침 404가 발생하는 원인과 해결책은 무엇인가.
- 빌드 산출물을 Spring Boot에 포함해 배포하는 방식과 CDN 분리 배포 방식을 각각 언제 선택하는가.
- VITE_ 접두사가 붙은 환경 변수에 비밀 값을 넣으면 안 되는 이유는 무엇인가.
React / Vue에서 같이 읽기
- React Fragment: 불필요한 래퍼 없이 요소를 묶는 방법React Fragment는 여러 자식 요소를 추가 DOM 노드 없이 하나로 묶어 반환하게 해 주는 문법 요소다. 하나의 루트 요소 제약을 래퍼 없이 해결해 DOM 깊이와 CSS 레이아웃 문제를 예방하는 원리와 key 사용법을 정리한다.
- React Hook: 함수 컴포넌트의 상태와 로직 재사용React Hook은 함수 컴포넌트에서 상태 관리와 사이드 이펙트 처리를 가능하게 하는 함수들의 모음이다. 훅의 등장 배경, 호출 순서 기반 동작 원리와 규칙, 커스텀 훅을 통한 로직 재사용까지 면접 관점에서 정리한다.
- React와 Spring Boot 연동 구조: CORS, 프록시, 배포 전략React SPA와 Spring Boot REST API 서버를 분리해 구성하는 아키텍처를 다룬다. 개발 환경의 CORS와 프록시 설정, 정적 빌드 산출물의 통합 서빙과 분리 서빙 전략, 새로고침 404 해결까지 원리 중심으로 설명한다.
- Vue 3와 Firebase 이메일 인증: 토큰 기반 인증 흐름Firebase Authentication은 이메일 인증을 서버 구현 없이 제공하는 관리형 인증 서비스다. ID 토큰과 리프레시 토큰의 흐름, Vue 3 컴포저블 통합, 라우터 가드와 세션 유지, 보안 주의점을 원리 중심으로 정리한다.
이 개념, 내 코드에서는 어떻게 물어볼까요
데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.
공고 넣고 시작하기