SpringBoot3 x Thymeleaf で標準のレイアウトを使用する

エキサイト株式会社メディア事業部エンジニアの佐々木です。SpringBoot3でMPAアプリケーションを開発する場合に、Thymeleafテンプレートを使用することは、ほぼデファクトになるかと思います。今回はThymeleafのフラグメントを使用した簡単なレイアウトファイルの作成をご紹介します。

前提

$ java --version
openjdk 21.0.2 2024-01-16 LTS
OpenJDK Runtime Environment Corretto-21.0.2.13.1 (build 21.0.2+13-LTS)
OpenJDK 64-Bit Server VM Corretto-21.0.2.13.1 (build 21.0.2+13-LTS, mixed mode, sharing)

$ ./gradlew --version

Welcome to Gradle 8.6!
------------------------------------------------------------
Gradle 8.6
------------------------------------------------------------

Build time:   2024-02-02 16:47:16 UTC
Revision:     d55c486870a0dc6f6278f53d21381396d0741c6e

Kotlin:       1.9.20
Groovy:       3.0.17
Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM:          21.0.2 (Amazon.com Inc. 21.0.2+13-LTS)
OS:           Mac OS X 12.5 aarch64

./gradlew bootRun

> Task :bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.3)

設定ファイル

build.gradleは下記にように設定されています。

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.3'
    id 'io.spring.dependency-management' version '1.1.4'
}

group = 'jp.co.excite'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '21'
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

bootRun {
    sourceResources sourceSets.main
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // Thymeleafを使用するモジュール
    implementation 'org.springframework.boot:spring-boot-starter-web'  // Webが表示できるようにするモジュール
}

tasks.named('test') {
    useJUnitPlatform()
}

今回のファイル構成

下記は、Thymeleafに必要な部分のみになります。

project_root
└ src
   └ main        
     └ resoureces
       └ templates
         └ index.html
         └ layoutFile.html  

エンドポイントの設定

下記のようなコントローラーを設定します。リクエストがきたら、index.htmlテンプレート返却するような処理になります。

@Controller
@RequestMapping("")
public class RootController {

    @GetMapping("")
    public String index(){
        return "index";
    }
}

レイアウトHTMLを作成する

レイアウト用のHTMLを作成します。ファイル名は、layoutFile.htmlとします。レイアウトファイルでは、 th:fragment を使用して構築していきます。

layoutFile.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"  th:fragment="layout(title, content, header, footer)"> <!--  th:fragment を宣言します -->
<head>
    <meta charset="UTF-8">
    <title th:replace="${title}">Title</title> <!-- th:fragmentで宣言したフラグメント名layoutの title を利用する -->
</head>
<body>
<header>
    <h1 th:replace="${header}">ヘッダーです</h1> <!-- th:fragmentで宣言したフラグメント名layoutの header を利用する -->
</header>
<hr/>
<main>
    <article>
        <h2>メインのコンテンツです。</h2>
        <div th:replace="${content}"> <!-- th:fragmentで宣言したフラグメント名layoutの content を利用する -->
            demo用のコンテンツです
        </div>
    </article>
</main>
<hr/>
<footer>
    <b>フッターです</b>
    <div>
       <p th:insert="${footer}">Copyright &copy; excite.co.jp demo</p>  <!-- th:fragmentで宣言したフラグメント名layoutのfooterを利用する -->
    </div>
</footer>
</body>
</html>

th:fragment="layout(title, content, header, footer)として4つの引数を取ることができますが、この値を置き換えたいHTMLタグにth:replaceth:insertを使用し設定します。

テンプレートを利用する側のHTML

続いては利用する側のHTMLを書いていきます。ファイル名は index.html とします。 利用する側は、 th:replace を使用します。このときに 前項で作成したlayoutFile.htmlを読み込みながら、layoutフラグメントをそれぞれの値で書き換えいくように設定します。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layoutFile::layout(~{::title}, ~{::#content}, ~{::index_h1}, _)}">
<head>
    <meta charset="UTF-8">
    <title>sample sample</title>
</head>
<body>
<header>
  <h1 th:fragment="index_h1"> index.htmlのh1です </h1>
</header>
<div id="content">
    <p>コンテンツの中身です。</p>
</div>

</body>
</html>

th:replace="~{layoutFile::layout(~{::title}, ~{::#content}, ~{::index_h1}, _)}" これの説明をします。

  1. th:replaceで置き換える指示をします。
  2. ~{layoutFile::layout(〜〜〜) layoutFile.htmlのlayoutフラグメントを呼び出します。下記は、layoutの引数の中身を解説します。
    1. ~{::title} titleタグを引数に入れます
    2. ~{::#content} HTML内の id="content" を引数に入れます
    3. ~{::index_h1} HTML内の th:fragment="index_h1"を引数に入れます
    4. _ トークンなし引数を渡します。Thymeleafとして処理せずに記述してあるlayoutFile.htmlに書いてあるHTMLをそのまま出力してくれます。

出力は下記になります。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>sample sample</title>
</head>
<body>
<header>
    <h1>index.htmlのh1です</h1>
</header>
<hr/>
<main>
    <article>
        <h2>メインのコンテンツです。</h2>
        <div id="content">
    <p>index.htmlのコンテンツの中身です。</p>
</div>
    </article>
</main>
<hr/>
<footer>
    <b>フッターです</b>
    <p>Copyright &copy; excite.co.jp demo</p>
</footer>
</body>
</html>

layoutFile.htmlの指定した場所のみの書き換えができました。

まとめ

Thymeleafは標準レイアウトのみで、上記のようなレイアウトファイルを使用した作りが可能です。参照もHTMLタグでもidでも指定可能なので割と柔軟性はあるかと思います。MPAで開発する際には活用したいところです。

最後に

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。長期インターンも歓迎していますので、興味があれば連絡いただければと思います。

募集職種一覧はこちらになります!(カジュアルからもOK) www.wantedly.com