웹 화면을 개발할 때, 여러 페이지에 공통으로 들어가는 부분이 많다. 예를 들어, 모든 페이지의 <head> 안에는 공통 CSS, 자바스크립트, 파비콘 같은 것들이 들어간다. 또 <header> 메뉴, <footer> 저작권 표시처럼 사이트 전반에 반복적으로 쓰이는 영역도 있다.
이런 코드를 매번 복사해 넣는다면 유지보수가 매우 힘들어진다. 한 부분을 수정할 때 모든 페이지를 고쳐야 하기 때문이다.
타임리프는 이런 문제를 해결하기 위해 템플릿 조각(fragment) 과 레이아웃(layout) 기능을 제공한다. 이번 글에서는 레이아웃을 적용해서 공통 구조를 만들고, 각 페이지에서 필요한 정보만 교체하는 방법을 알아본다.
1. <head> 부분에 레이아웃 적용하기
먼저 공통 CSS/JS가 들어 있는 <head>를 만들어 두고, 페이지마다 필요한 제목과 추가 CSS만 전달하는 방식이다.
레이아웃 베이스 (base.html)
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="common_header(title, links)">
<title th:replace="${title}">레이아웃 타이틀</title>
<!-- 공통 -->
<link rel="stylesheet" th:href="@{/css/awesomeapp.css}">
<link rel="shortcut icon" th:href="@{/images/favicon.ico}">
<script th:src="@{/sh/scripts/codebase.js}"></script>
<!-- 추가 -->
<th:block th:replace="${links}" />
</head>
</html>
여기서 th:fragment="common_header(title, links)"는 "title과 links를 받아서 렌더링할 수 있는 조각(fragment)"이라는 뜻이다.
실제 페이지 (layoutMain.html)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="template/layout/base :: common_header(~{::title}, ~{::link})">
<title>메인 타이틀</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
<link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">
</head>
<body>
메인 컨텐츠
</body>
</html>
여기서 ~{::title} 은 현재 페이지의 <title> 태그를, ~{::link} 는 현재 페이지의 <link> 태그들을 그대로 전달한다.
렌더링 결과
<head>
<title>메인 타이틀</title>
<!-- 공통 -->
<link rel="stylesheet" href="/css/awesomeapp.css">
<link rel="shortcut icon" href="/images/favicon.ico">
<script src="/sh/scripts/codebase.js"></script>
<!-- 추가 -->
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/themes/smoothness/jquery-ui.css">
</head>
공통 <head> 리소스는 유지되면서, 각 페이지에서 지정한 <title>과 <link>가 추가된 것을 확인할 수 있다.
2. <html> 전체를 레이아웃으로 확장하기
이번에는 <html> 전체 구조를 공통 레이아웃으로 두고, 각 페이지에서 제목과 본문만 교체하는 방식이다.
레이아웃 파일 (layoutFile.html)
<!DOCTYPE html>
<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
<title th:replace="${title}">레이아웃 타이틀</title>
</head>
<body>
<h1>레이아웃 H1</h1>
<div th:replace="${content}">
<p>레이아웃 컨텐츠</p>
</div>
<footer>레이아웃 푸터</footer>
</body>
</html>
여기서 layout (title, content)는 두 개의 영역을 받는다: 제목(title)과 본문(content).
실제 페이지 (layoutExtendMain.html)
<!DOCTYPE html>
<html th:replace="~{template/layoutExtend/layoutFile :: layout(~{::title}, ~{::section})}"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>메인 페이지 타이틀</title>
</head>
<body>
<section>
<p>메인 페이지 컨텐츠</p>
<div>메인 페이지 포함 내용</div>
</section>
</body>
</html>
여기서 ::title은 <title> 태그, ::section은 <section> 태그를 전달한다.
렌더링 결과
<!DOCTYPE html>
<html>
<head>
<title>메인 페이지 타이틀</title>
</head>
<body>
<h1>레이아웃 H1</h1>
<section>
<p>메인 페이지 컨텐츠</p>
<div>메인 페이지 포함 내용</div>
</section>
<footer>레이아웃 푸터</footer>
</body>
</html>
즉, 기본 뼈대(레이아웃)는 유지하면서, 각 페이지에서 작성한 <title>과 <section>만 교체된 결과를 얻는다.
마무리하며
타임리프의 레이아웃 기능은 공통 부분과 개별 부분을 분리해서 관리할 수 있게 도와준다.
덕분에 프로젝트 전체의 일관성을 유지할 수 있고, 공통 코드의 수정도 한 곳에서만 하면 되기 때문에 유지보수가 훨씬 수월하다. 또한 페이지마다 필요한 리소스를 쉽게 추가할 수 있어 개발 생산성도 올라간다.
정리하면, 템플릿 조각이 “작은 단위의 재사용”이라면, 레이아웃은 “전체 페이지 구조의 재사용”이라고 이해하면 쉽다.
감사합니다.
'타임리프' 카테고리의 다른 글
| [타임리프] 체크박스 (0) | 2025.09.17 |
|---|---|
| [Thymeleaf] 템플릿 조각 (0) | 2025.09.16 |
| [Thymeleaf] 자바스크립트 인라인 (0) | 2025.09.16 |
| [Thymeleaf] 블록 (0) | 2025.09.16 |
| [Thymeleaf] 주석 (0) | 2025.09.16 |
