Tech Blog

Thymeleaf + TypeScript + CSS の修正が画面に反映されなかった原因と対策

Thymeleaf TypeScript CSS Spring Boot

はじめに

Spring Boot + Thymeleaf + TypeScript + TailwindCSSでの開発中、こんな経験をしました。

  • TypeScriptのコードを修正した
  • ブラウザを更新した
  • 変化がない

「キャッシュ?」と疑ってハードリフレッシュ(Ctrl+Shift+R)してみる。
それでも変化がない。

しばらく悩んで、ようやく原因がわかりました。ビルドしていなかった。

この記事は「当たり前のことじゃないか」と思われそうですが、Spring Boot + フロント資産の混在構成では、これがかなり起きやすいポイントです。


構成の説明

このプロジェクトの構成:

src/
  main/
    resources/
      templates/          ← Thymeleafテンプレート(.html)
      static/
        css/
          tailwind.css    ← TailwindCSSのソース
        js/
          app.ts          ← TypeScriptのソース
        dist/
          tailwind.min.css ← ビルド後のCSS(これが配信される)
          app.js           ← ビルド後のJS(これが配信される)

ブラウザに配信されるのは dist/ 配下のビルド後ファイルです。
ソースファイル(app.ts, tailwind.css)を直接修正しても、ビルドしなければ dist/ が更新されません。


なぜ間違えやすいか

Thymeleafテンプレートは即反映

Thymeleafの .html ファイルは、spring.thymeleaf.cache=false を設定すればビルド不要でブラウザ更新のたびに最新が反映されます。

# application.yml(開発時)
spring:
  thymeleaf:
    cache: false

これに慣れると、TypeScriptやCSSも同じ感覚で修正してしまいます。

修正して保存→ブラウザ更新のルーティンが崩れる

Thymeleaf修正では 保存 → ブラウザ更新 で完結します。
フロント資産では 保存 → ビルド → ブラウザ更新 が必要です。この「ビルド」ステップを忘れます。


ビルドコマンド

// package.json
{
  "scripts": {
    "build:css": "npx tailwindcss -i ./src/main/resources/static/css/tailwind.css -o ./src/main/resources/static/dist/tailwind.min.css --minify",
    "build:ts": "npx tsc",
    "build": "npm run build:css && npm run build:ts",
    "watch": "npm run build:css -- --watch & npx tsc --watch"
  }
}

npm run watch で監視モードにすると、ファイル保存時に自動ビルドが走ります。


開発時の watch モード設定

# ターミナル1: Spring Boot
./mvnw spring-boot:run

# ターミナル2: フロント資産の監視ビルド
npm run watch

2つのプロセスを並行して動かします。
VS Code では別タブのターミナルを使うか、タスクとして定義しておくと便利です。

VS Code タスクとして定義

// .vscode/tasks.json
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "watch-frontend",
      "type": "shell",
      "command": "npm run watch",
      "isBackground": true,
      "problemMatcher": []
    }
  ]
}

反映されない場合のチェックリスト

修正しても反映されない場合、この順番で確認します。

  1. ビルドしたか?

    • npm run build または npm run watch が動いているか確認
    • ビルドエラーが出ていないか確認(ターミナルを見る)
  2. 正しいファイルを修正したか?

    • ソースファイル(app.ts)を修正したか、ビルド後ファイル(app.js)を誤修正していないか
  3. Thymeleafキャッシュは無効化されているか?

    • spring.thymeleaf.cache=false の設定確認
  4. ブラウザキャッシュが残っていないか?

    • ハードリフレッシュ(Ctrl+Shift+R または Ctrl+F5)
    • 開発者ツール → Network タブで「Disable cache」にチェック
    • ?v=タイムスタンプ のクエリパラメータをHTMLに付けてキャッシュバスティング
  5. Spring Bootのキャッシュは無効化されているか?

    • spring.web.resources.cache.period=0 を設定

本番ビルドでのキャッシュバスティング

本番環境では、ブラウザがCSSやJSを長期キャッシュする設定が入ることが多いです。
ファイル名にハッシュを付けることでキャッシュを無効化します。

<!-- Thymeleaf テンプレート -->
<!-- 単純なバージョン(デプロイのたびに変える) -->
<link rel="stylesheet" th:href="@{/dist/tailwind.min.css(v=${buildVersion})}">
<script th:src="@{/dist/app.js(v=${buildVersion})}"></script>
// Controller でビルドバージョンを渡す
@ModelAttribute("buildVersion")
public String buildVersion() {
    return System.getenv("BUILD_VERSION"); // CIで環境変数として設定
}

まとめ

Thymeleaf + フロント資産の混在構成での鉄則:

  1. フロント資産はビルドが必要(HTMLとは違う)
  2. 開発時は watch モードで自動ビルド
  3. 反映されないときは ビルド → ハードリフレッシュ の順で確認
  4. 本番はキャッシュバスティング(バージョンクエリ or ファイル名ハッシュ)

「Thymeleafのファイルは即反映されるのになぜ…」の答えは「ビルド不要なファイルとビルド必要なファイルが混在している」からです。
これを最初に理解しておくと、無駄なデバッグ時間を大幅に削減できます。

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

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