Why We Chose Spring Boot Backend + Vue 3 Frontend Separation, and How We Connected Them
Introduction
When building the customer-facing DVD rental app, there was a structure we decided on from the start.
- Backend: Spring Boot (REST API) → Port 8082
- Frontend: Vue 3 + Vite → Port 5173 (development)
You might wonder “why not use Thymeleaf?” — let me answer that upfront.
We built the admin panel with Thymeleaf. That was the right choice for an admin panel.
However, the customer-facing app had UX requirements that were difficult to achieve with Thymeleaf.
This article is not about “which is the right choice” but about “how we made that decision.”
Cases Where Thymeleaf Is Sufficient vs. Not
Cases Where Thymeleaf Excels
For admin panels with repeated “form submission → result display” cycles, Thymeleaf is excellent.
- Returns HTML from the server on each page transition
- Tight integration with Spring Security, CSRF protection built in
- Easy server-side validation and error display
Thymeleaf was sufficient for the admin panel.
Why We Chose Vue 3
There was UX we wanted to achieve in the customer-facing app.
- Instant filter and search feedback — “Results change without pressing a search button” experience
- Smooth cart drawer opening and closing — Interactions without page transitions
- Different layouts for mobile and desktop — Dynamic display switching based on viewport
- Future API sharing with native apps — The same REST API can be called from iOS/Android apps
These aren’t “impossible” with Thymeleaf, but you’d need to write a lot of JavaScript.
If we’re going to write JS anyway, componentizing with Vue 3 is more maintainable.
Actual Structure
dvd-rental-customer-app/
backend/ ← Spring Boot (Port 8082)
src/main/java/
api/ ← REST API controllers
service/ ← Business logic
frontend/ ← Vue 3 + Vite (Port 5173 in development)
src/
api/ ← HTTP client for backend
components/ ← Vue components
CORS and Proxy Settings for Development
The first hurdle with a separated architecture is CORS.
Calling from the frontend (port 5173) to the backend (port 8082) causes the browser to throw a CORS error.
Solution 1: Vite’s Dev Proxy (Development)
// vite.config.ts
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8082',
changeOrigin: true,
}
}
}
})
In development, Vite forwards requests to /api to the backend, so CORS doesn’t occur.
Solution 2: Spring Boot CORS Configuration (Production)
In production, the frontend and backend may be on different domains or ports.
In that case, allow CORS on the Spring Boot side.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://your-frontend-domain.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true);
}
}
Production Build Integration
For production, we build Vue with npm run build and serve the output as Spring Boot’s static resources.
<!-- pom.xml (excerpt) -->
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<executions>
<execution>
<id>npm build</id>
<goals><goal>npm</goal></goals>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
</plugin>
A single mvn package generates a jar that includes the frontend build.
Actual Pain Points with the Separated Architecture
① Need to Start 2 Processes During Development
Start Vite, then start Spring Boot.
Once you’re used to it, it takes 10 seconds, but initially you forget one and wonder “why isn’t this working?”
② Deploying Without Building the Frontend
It can happen that mvn package runs while the frontend still has an old build.
It’s important to explicitly define the order in CI/CD to always run npm run build first.
③ Type Mismatches
The response types from the backend and the TypeScript types in the frontend tend to diverge.
You need to either share schema definitions with OpenAPI, or have a habit of aligning them regularly.
Summary
| Aspect | Thymeleaf | Vue 3 + REST API |
|---|---|---|
| Initial development speed | Fast | Somewhat slow (setup required) |
| Interactive UI | Difficult | Strength |
| API sharing (mobile, etc.) | Not possible | Possible |
| Spring Security integration | Easy | Custom implementation needed |
| Deployment complexity | Simple | Somewhat complex |
Thymeleaf for the admin panel, Vue 3 for the customer-facing app.
This split was the optimal choice for this project.
The important thing is not “which is better” but “choose based on what you want to build.”