Общий макет и статические файлы
У всех страниц блога одинаковые шапка с меню, подвал и подключение стилей. Копировать их в каждый шаблон — значит править потом в трёх местах. В Thymeleaf общие куски выносят во фрагменты и вставляют одной строкой. Стили и картинки — статические файлы: Spring Boot отдаёт их без обработки.
Файл с фрагментами
<!-- src/main/resources/templates/fragments/layout.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="head(title)">
<meta charset="UTF-8">
<title th:text="${title} + ' | Блог'">Блог</title>
<link rel="stylesheet" th:href="@{/css/site.css}">
</head>
<body>
<header th:fragment="header">
<a th:href="@{/}">Блог</a>
<a th:href="@{/posts/new}">Новая статья</a>
</header>
<footer th:fragment="footer">
<p>Анна и Олег, 2026</p>
</footer>
</body>
</html>th:fragment даёт куску имя. У фрагмента head есть параметр title — так каждая страница передаст свой заголовок окна.
Вставка фрагментов
<!-- src/main/resources/templates/posts/list.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('Статьи')}"></head>
<body>
<header th:replace="~{fragments/layout :: header}"></header>
<h1>Статьи</h1>
<!-- список статей, как в уроке о шаблонах -->
<footer th:replace="~{fragments/layout :: footer}"></footer>
</body>
</html>th:replace="~{fragments/layout :: header}" заменяет тег фрагментом header из файла templates/fragments/layout.html. В скобках после имени передают параметры: на странице статьи это будет head(${post.title}). Так же оформите show.html и form.html.
Стили
/* src/main/resources/static/css/site.css */
body { font-family: sans-serif; max-width: 720px; margin: 0 auto; }
.error { color: #b00020; }Всё из папки static доступно от корня сайта: файл static/css/site.css откроется по адресу /css/site.css. Ссылку строит th:href="@{/css/site.css}" — она учтёт префикс, если приложение когда-нибудь запустят не в корне сервера.
Результат
curl http://localhost:8080/ теперь возвращает собранную страницу (фрагмент):
<head>
<meta charset="UTF-8">
<title>Статьи | Блог</title>
<link rel="stylesheet" href="/css/site.css">
</head>
<body>
<header>
<a href="/">Блог</a>
<a href="/posts/new">Новая статья</a>
</header>Заголовок окна пришёл из параметра, шапка — из общего файла. Поменяв меню в layout.html, вы поменяете его на всех страницах.
Где что лежит
| Путь | Адрес в браузере |
|---|---|
src/main/resources/static/css/site.css | /css/site.css |
src/main/resources/static/img/logo.png | /img/logo.png |
src/main/resources/templates/posts/list.html | Только через контроллер |
Шаблоны напрямую по адресу недоступны — их показывает только контроллер.
Частая ошибка
Если ошибиться в пути к фрагменту, например написать ~{fragments/layot :: header}, страница ответит 500, а журнал сообщит Error resolving template [fragments/layot]. Путь в ~{...} считается от папки templates, а после :: идёт имя из th:fragment. И не кладите CSS в templates: файлы оттуда браузер не получит, стили просто не применятся.