Tech Blog

Spring Boot バックエンド + Vue 3 フロントエンドの分離構成を選んだ理由と、実際の繋ぎ方

Vue3 Spring Boot Architecture CORS

はじめに

顧客向けDVDレンタルアプリを作るとき、最初に決めた構成があります。

  • バックエンド: Spring Boot(REST API)→ ポート8082
  • フロントエンド: Vue 3 + Vite → ポート5173(開発時)

「なぜThymeleafで作らないのか」という疑問が来そうなので、先に答えておきます。

管理画面はThymeleafで作りました。それは管理画面として正しい選択でした。
しかし顧客向けアプリは、Thymeleafでは実現しにくいUXがありました。

この記事は「どちらが正解か」ではなく「どう判断したか」の話です。


Thymeleaf で十分なケースとそうでないケース

Thymeleaf が向いているケース

管理画面のような「フォーム送信→結果表示」の繰り返しは、Thymeleafが非常に得意です。

  • ページ遷移ごとにサーバーからHTMLを返す
  • Spring Securityとの統合が密で、CSRF保護が自動で入る
  • サーバーサイドでのバリデーションとエラー表示が簡単

管理画面はこれで十分でした。

Vue 3 を選んだ理由

顧客向けアプリで実現したかったUXがありました。

  1. フィルタ・検索の即時反映 — 「検索ボタンを押さなくても結果が変わる」体験
  2. カートドロワーのスムーズな開閉 — ページ遷移なしのインタラクション
  3. モバイルとデスクトップで異なるレイアウト — ビューポートに応じた動的な表示切り替え
  4. 将来的なネイティブアプリとのAPI共有 — 同じREST APIをiOS/Androidアプリからも叩ける

これらはThymeleafで「不可能」ではありませんが、JavaScriptを大量に書くことになり、
どうせJSを書くならVue 3でコンポーネント化した方がメンテナブルです。


実際の構成

dvd-rental-customer-app/
  backend/           ← Spring Boot(ポート8082)
    src/main/java/
      api/           ← REST APIコントローラ
      service/       ← ビジネスロジック
  frontend/          ← Vue 3 + Vite(開発時ポート5173)
    src/
      api/           ← バックエンドへのHTTPクライアント
      components/    ← Vueコンポーネント

開発時のCORSとProxy設定

分離構成の最初の壁がCORSです。
フロント(ポート5173)からバックエンド(ポート8082)を呼ぶと、ブラウザがCORSエラーを出します。

解決策1:Viteのdevプロキシ(開発時)

// vite.config.ts
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8082',
        changeOrigin: true,
      }
    }
  }
})

開発時はViteが /api へのリクエストをバックエンドに転送するので、CORSが発生しません。

解決策2:Spring BootのCORS設定(本番時)

本番環境ではフロントとバックが別ドメイン or 別ポートになる可能性があります。
その場合はSpring Boot側でCORSを許可します。

@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);
    }
}

本番ビルドの統合

本番環境では npm run build でVueをビルドし、その成果物をSpring Bootの static リソースとして配信する構成にしました。

<!-- pom.xml(抜粋) -->
<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>

mvn package 一発でフロントのビルドも含めた jar が生成されます。


分離構成で実際に困ったこと

① 開発時に2プロセス起動が必要

Viteを起動してから、Spring Bootも起動する。
慣れれば10秒で済みますが、最初はセットを忘れて「なんで動かないんだ」となります。

② フロントのビルドを忘れてデプロイする

mvn package を実行してもフロントが古いビルドのまま、ということが起きます。
CI/CDで npm run build を必ず先に実行する順序を明示することが重要です。

③ 型の不一致

バックエンドのレスポンス型とフロントのTypeScript型が乖離しやすいです。
OpenAPIでスキーマ定義を共有するか、定期的に合わせる習慣が必要です。


まとめ

観点ThymeleafVue 3 + REST API
初期開発速度速いやや遅い(環境構築あり)
インタラクティブなUI難しい得意
API共有(モバイル等)不可可能
Spring Securityとの統合簡単自前実装が必要
デプロイの複雑さシンプルやや複雑

管理画面はThymeleaf、顧客向けはVue 3。
この使い分けが今回のプロジェクトでは最適解でした。
「どちらが優れているか」ではなく「何を作りたいかで選ぶ」ことが大事です。


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

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

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

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