Tech Blog

Spring Boot で Vue3/React のフロントビルドを同梱して自動配信する構成

Spring Boot Vue3 Vite Maven

はじめに

Vue 3 + Spring Boot の分離構成で開発していると、本番デプロイ時に悩む問題があります。

  • フロントのビルド成果物はどこに置く?
  • Spring BootはどうやってVueの静的ファイルを配信する?
  • Mavenでビルドするとき、フロントのnpmビルドも一緒に実行できないか?

この記事は「mvn package 一発でフロントのビルドも含めた実行可能jarを作る構成」の解説です。


最終的な構成

dvd-rental-customer-app/
  backend/
    pom.xml                    ← Maven設定(frontendビルドも含む)
    src/main/resources/
      static/                  ← フロントのビルド成果物の配置先
        (ビルド後に自動配置される)
  frontend/
    package.json
    vite.config.ts
    src/
      App.vue
      ...

ステップ1:Viteのビルド出力先を変更

デフォルトでは frontend/dist/ に出力されますが、Spring Bootが配信できる場所に変更します。

// 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,  // ビルド前に static/ をクリア
  },
  server: {
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:8082',
        changeOrigin: true,
      }
    }
  }
})

npm run build すると backend/src/main/resources/static/ に成果物が入ります。


ステップ2:frontend-maven-plugin の設定

<!-- backend/pom.xml -->
<build>
    <plugins>
        <!-- Nodeのインストールとnpmビルドを自動実行 -->
        <plugin>
            <groupId>com.github.eirslett</groupId>
            <artifactId>frontend-maven-plugin</artifactId>
            <version>1.15.0</version>
            <configuration>
                <!-- フロントのディレクトリ(pom.xmlからの相対パス) -->
                <workingDirectory>../frontend</workingDirectory>
                <!-- 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>

ステップ3:Spring Boot の静的ファイル配信設定

Spring Bootはデフォルトで classpath:/static/ を静的ファイルのルートとして使います。
特別な設定は不要ですが、念のため明示する場合:

# application.yml
spring:
  web:
    resources:
      static-locations: classpath:/static/

ステップ4:Vue Routerとの組み合わせ(SPA対応)

Vue RouterでHistory APIを使っている場合、ブラウザで直接 /films/123 にアクセスすると
Spring Bootが /films/123 というパスを探してしまい 404 になります。

// SPAのルーティングをSpring Boot側で受け取る設定
@Controller
public class SpaForwardController {
    
    @GetMapping(value = {"/", "/{path:[^\\.]*}", "/{path:^(?!api|actuator).*$}/**"})
    public String forward() {
        return "forward:/index.html";
    }
}
  • /api/** → REST APIエンドポイントで処理
  • それ以外 → index.html を返す(Vue Routerが受け取って処理)

ビルド時の動作

# mvn package を実行すると...
mvn -f backend/pom.xml package

# 1. frontend-maven-plugin が Node.js をダウンロード(初回のみ)
# 2. npm install を実行
# 3. npm run build を実行 → backend/src/main/resources/static/ に出力
# 4. Spring Boot の jar にすべて同梱
# 5. 実行可能 jar が生成される

生成されたjarを実行するだけでフロント+バックの両方が動きます。

java -jar backend/target/dvd-rental-customer-backend-0.1.0-SNAPSHOT.jar

開発時のフロー

本番ビルドと開発フローを分けることが重要です。

# 開発時(2プロセス並行)
# ターミナル1: Viteの開発サーバー(HMR有効、ポート5173)
cd frontend && npm run dev

# ターミナル2: Spring Boot(ポート8082)
cd backend && ../mvnw spring-boot:run -Dspring-boot.run.profiles=local

開発時はViteのHMR(Hot Module Replacement)が使えるため、ファイル変更が即座にブラウザに反映されます。


Docker との組み合わせ

Dockerfileでも同様の手順でビルドできます。

# マルチステージビルド
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/ .
# フロントのビルド成果物を 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 を使うと mvn package 一発でフロントのビルドも含めた jar が作れる
  • Viteのビルド出力先を backend/src/main/resources/static/ に向けることで自動同梱される
  • Vue RouterのHistory APIを使う場合、Spring Boot側でSPAフォワーディングが必要
  • 開発時はViteのHMRを活かして2プロセス並行で動かす

この構成にすることで、デプロイ時に「フロントを別途デプロイする」手間がなくなり、jarを1つコピーするだけで済みます。


このシリーズの記事マップ

dvdrental 管理アプリと対になるエンドユーザー向けDVDレンタルアプリを作っている話 — Vue 3 + Spring Boot の全体構成と記事マップ

気軽にメッセージください

技術相談・ご感想・ご質問があればメッセージをお願いします。