การ Bundle Frontend Build ของ Vue3/React กับ Spring Boot เพื่อ Serve อัตโนมัติ
บทนำ
เมื่อพัฒนาด้วย architecture แบบ Vue 3 + Spring Boot แยกกัน มีความท้าทายตอน production deployment
- จะวาง frontend build artifacts ไว้ที่ไหน?
- Spring Boot จะ serve static files ของ Vue ได้อย่างไร?
- เมื่อ build ด้วย Maven npm build ของ frontend จะรันพร้อมกันได้ไหม?
บทความนี้อธิบาย “configuration สำหรับสร้าง executable jar ที่รวม frontend build ด้วย mvn package คำสั่งเดียว”
โครงสร้างสุดท้าย
dvd-rental-customer-app/
backend/
pom.xml ← Maven config (รวม frontend build)
src/main/resources/
static/ ← จุดหมายของ frontend build artifacts
(วาง auto หลัง build)
frontend/
package.json
vite.config.ts
src/
App.vue
...
Step 1: เปลี่ยน Vite Build Output Destination
ค่าเริ่มต้น output ไปที่ frontend/dist/ แต่เราเปลี่ยนเป็น location ที่ Spring Boot serve ได้
// frontend/vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
build: {
outDir: resolve(__dirname, '../backend/src/main/resources/static'),
emptyOutDir: true, // Clear static/ ก่อน build
},
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8082',
changeOrigin: true,
}
}
}
})
การรัน npm run build จะวาง artifacts ใน backend/src/main/resources/static/
Step 2: การ Configure frontend-maven-plugin
<!-- backend/pom.xml -->
<build>
<plugins>
<!-- รัน Node installation และ npm build โดยอัตโนมัติ -->
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.15.0</version>
<configuration>
<!-- Frontend directory (relative path จาก pom.xml) -->
<workingDirectory>../frontend</workingDirectory>
<!-- จุดหมาย installation ของ Node/npm -->
<installDirectory>target</installDirectory>
</configuration>
<executions>
<!-- ติดตั้ง Node.js และ npm -->
<execution>
<id>install-node-and-npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
<configuration>
<nodeVersion>v22.0.0</nodeVersion>
<npmVersion>10.5.2</npmVersion>
</configuration>
</execution>
<!-- npm install -->
<execution>
<id>npm-install</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>install</arguments>
</configuration>
</execution>
<!-- npm run build -->
<execution>
<id>npm-build</id>
<goals>
<goal>npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Step 3: การ Configure Spring Boot Static File Serving
Spring Boot ใช้ classpath:/static/ เป็น static file root ค่าเริ่มต้น
ไม่ต้องการ configuration พิเศษ แต่ถ้าต้องการระบุอย่างชัดเจน:
# application.yml
spring:
web:
resources:
static-locations: classpath:/static/
Step 4: การรวมกับ Vue Router (SPA Support)
เมื่อใช้ History API กับ Vue Router การเข้าถึง /films/123 โดยตรงใน browser
จะทำให้ Spring Boot หา path /films/123 และ return 404
// Setting สำหรับให้ Spring Boot handle SPA routing
@Controller
public class SpaForwardController {
@GetMapping(value = {"/", "/{path:[^\\.]*}", "/{path:^(?!api|actuator).*$}/**"})
public String forward() {
return "forward:/index.html";
}
}
/api/**→ จัดการโดย REST API endpoints- ทุกอย่างอื่น → Return
index.html(Vue Router จัดการ)
พฤติกรรมตอน Build
# เมื่อรัน mvn package...
mvn -f backend/pom.xml package
# 1. frontend-maven-plugin download Node.js (ครั้งแรกเท่านั้น)
# 2. รัน npm install
# 3. รัน npm run build → output ไป backend/src/main/resources/static/
# 4. ทุกอย่าง bundle เข้า Spring Boot jar
# 5. Executable jar ถูกสร้าง
แค่รัน jar ที่สร้าง ทั้ง frontend และ backend ก็ทำงานได้
java -jar backend/target/dvd-rental-customer-backend-0.1.0-SNAPSHOT.jar
Development Flow
สำคัญที่จะแยก production builds ออกจาก development flow
# Development (2 processes ทำงานพร้อมกัน)
# Terminal 1: Vite dev server (HMR enabled, port 5173)
cd frontend && npm run dev
# Terminal 2: Spring Boot (port 8082)
cd backend && ../mvnw spring-boot:run -Dspring-boot.run.profiles=local
ระหว่าง development Vite’s HMR (Hot Module Replacement) ใช้งานได้ ดังนั้นการเปลี่ยนแปลง file สะท้อนใน browser ทันที
การรวมกับ Docker
คุณสามารถทำตามขั้นตอนเดียวกันเมื่อ build กับ Docker
# Multi-stage build
FROM node:22-alpine AS frontend-build
WORKDIR /frontend
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ .
RUN npm run build
FROM eclipse-temurin:21-jdk AS backend-build
WORKDIR /app
COPY backend/ .
# Copy frontend build artifacts ไป static/
COPY --from=frontend-build /frontend/dist ./src/main/resources/static/
RUN ./mvnw package -DskipTests -Dfrontend.skip=true
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=backend-build /app/target/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
สรุป
- การใช้
frontend-maven-pluginช่วยให้สร้าง jar รวม frontend build ด้วยmvn packageเดียว - การชี้ Vite build output ไปที่
backend/src/main/resources/static/ทำให้ bundle อัตโนมัติ - เมื่อใช้ Vue Router’s History API ต้องมี SPA forwarding ฝั่ง Spring Boot
- ระหว่าง development รัน 2 processes ควบคู่เพื่อใช้ Vite’s HMR
ด้วย configuration นี้ ไม่ต้อง “deploy frontend แยกต่างหาก” ตอน deployment — แค่ copy jar เดียวก็พอ
แผนที่บทความของ Series นี้
→ การสร้างแอพ DVD Rental สำหรับผู้ใช้ควบคู่กับหน้าจัดการ — ภาพรวมโครงสร้าง Vue 3 + Spring Boot