Tech Blog

การ Bundle Frontend Build ของ Vue3/React กับ Spring Boot เพื่อ Serve อัตโนมัติ

by Tech Writer
Spring Boot Vue3 Vite Maven

บทนำ

เมื่อพัฒนาด้วย 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

ส่งข้อความได้ตามสบาย

กรุณาส่งข้อความ หากมีคำปรึกษาด้านเทคนิค ความคิดเห็น หรือคำถาม