공부 내용을 정리하고 앞으로의 학습에 이해를 돕기 위해 작성합니다.
템플릿 조각
웹 페이지에는 여러 페이지에서 공통으로 사용하는 UI 요소(헤더, 푸터, 사이드바 등)가 존재한다. 이를 매번 복사해서 사용하면 유지보수가 어렵고 비효율적이다. 타임리프는 이를 해결하기 위해 템플릿 조각(Fragment) 기능을 제공한다.
예제 컨트롤러
@Controller
@RequestMapping("/template")
public class TemplateController {
@GetMapping("/fragment")
public String template() {
return "template/fragment/fragmentMain";
}
}
조각 템플릿 정의: footer.html
<footer th:fragment="copy">
푸터 자리 입니다.
</footer>
<footer th:fragment="copyParam (param1, param2)">
<p>파라미터 자리 입니다.</p>
<p th:text="${param1}"></p>
<p th:text="${param2}"></p>
</footer>
- th:fragment="copy": 이름이 copy인 조각 정의
- th:fragment="copyParam (param1, param2)": 파라미터를 받는 조각 정의
조각 사용 예제: fragmentMain.html
<h2>부분 포함 insert</h2>
<div th:insert="~{template/fragment/footer :: copy}"></div>
<h2>부분 포함 replace</h2>
<div th:replace="~{template/fragment/footer :: copy}"></div>
<h2>부분 포함 단순 표현식</h2>
<div th:replace="template/fragment/footer :: copy"></div>
<h1>파라미터 사용</h1>
<div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터2')}"></div>
조각 포함 방식 설명
th:insert
<div th:insert="~{...}"></div>
- 지정한 조각을 현재 태그 내부에 포함
렌더링 결과
<div>
<footer>푸터 자리 입니다.</footer>
</div>
th:replace
<div th:replace="~{...}"></div>
- 현재 태그 자체를 조각으로 대체
렌더링 결과
<footer>푸터 자리 입니다.</footer>
단순 표현식
<div th:replace="template/fragment/footer :: copy"></div>
- ~{} 생략 가능 (표현식이 단순할 때만)
- 결과는 th:replace와 동일
파라미터 전달
<div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터2')}"></div>
- 조각에 파라미터를 넘길 수 있음
- 조각 내부에서는 ${param1}, ${param2}로 접근
렌더링 결과
<footer>
<p>파라미터 자리 입니다.</p>
<p>데이터1</p>
<p>데이터2</p>
</footer>
정리
- th:fragment: 조각 정의
- th:insert: 현재 태그 안에 조각 포함
- th:replace: 현재 태그를 조각으로 대체
- 조각 이름 뒤에 괄호를 붙여 파라미터 전달 가능
- 공통 레이아웃 구성 시 중복 제거와 유지보수 효율성을 극대화할 수 있는 기능
템플릿 레이아웃1
기존에는 공통으로 사용하는 코드를 레이아웃에 고정된 형태로 넣는 방식이었다면, 이번에는 레이아웃에 코드 조각을 동적으로 넘겨서 사용하는 방법에 대해 정리한다.
예를 들어, <head> 태그에는 모든 페이지에서 공통으로 사용하는 CSS, JavaScript 파일들이 있다. 이 공통 리소스들을 하나의 템플릿 파일에 모아두고, 각 페이지마다 필요한 추가적인 리소스를 개별적으로 전달해서 사용하는 방식이다.
컨트롤러 코드
@GetMapping("/layout")
public String layout() {
return "template/layout/layoutMain";
}
base.html – 공통 레이아웃 정의
경로: /resources/templates/template/layout/base.html
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="common_header(title,links)">
<title th:replace="${title}">레이아웃 타이틀</title>
<!-- 공통 리소스 -->
<link rel="stylesheet" type="text/css" media="all" th:href="@{/css/awesomeapp.css}">
<link rel="shortcut icon" th:href="@{/images/favicon.ico}">
<script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script>
<!-- 개별 페이지에서 전달한 추가 리소스 -->
<th:block th:replace="${links}" />
</head>
layoutMain.html – 실제 사용하는 페이지에서 조각 전달
경로: /resources/templates/template/layout/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>
결과 HTML
<!DOCTYPE html>
<html>
<head>
<title>메인 타이틀</title>
<!-- 공통 -->
<link rel="stylesheet" type="text/css" media="all" href="/css/awesomeapp.css">
<link rel="shortcut icon" href="/images/favicon.ico">
<script type="text/javascript" src="/sh/scripts/codebase.js"></script>
<!-- 추가 -->
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/themes/smoothness/jquery-ui.css">
</head>
<body>
메인 컨텐츠
</body>
</html>
정리
- common_header(~{::title}, ~{::link})
이 구문이 핵심이다. ~{::title}은 현재 페이지의 <title> 요소를, ~{::link}는 <link> 요소들을 전달한다. - base.html에서 th:fragment로 정의한 헤더 레이아웃에, 각 페이지에서 정의한 title과 link 태그를 동적으로 넘겨서 조합하는 구조이다.
- 이 방식은 레이아웃을 템플릿으로 정의해 두고, 필요한 부분만 각 페이지에서 채워 넣는 구조이다.
템플릿 레이아웃2
앞서 살펴본 템플릿 레이아웃 적용 방식은 <head> 영역처럼 문서의 일부분에만 코드 조각을 넘겨서 사용하는 방식이었다. 이번에는 이 개념을 더 확장하여, <html> 전체 구조를 템플릿 레이아웃으로 정의하고, 동적으로 필요한 부분만 넘겨서 사용하는 방법을 정리한다.
컨트롤러 코드
@GetMapping("/layoutExtend")
public String layoutExtends() {
return "template/layoutExtend/layoutExtendMain";
}
layoutFile.html – 전체 레이아웃 정의
경로: /resources/templates/template/layoutExtend/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>
- <html> 태그에 th:fragment="layout(title, content)" 속성을 선언하여 전체 HTML 문서를 레이아웃으로 정의한다.
- 내부에서는 <title>과 <div> 두 곳을 코드 조각으로 받을 수 있도록 th:replace="${...}"로 구성한다.
layoutExtendMain.html – 레이아웃을 확장하여 사용하는 페이지
경로: /resources/templates/template/layoutExtend/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>
- <html> 전체를 th:replace로 교체하여 layoutFile.html을 기반으로 확장 사용한다.
- 이때 <title>, <section> 요소를 각각 layoutFile.html에 정의된 title, content 인자에 넘겨준다.
결과 HTML
<!DOCTYPE html>
<html>
<head>
<title>메인 페이지 타이틀</title>
</head>
<body>
<h1>레이아웃 H1</h1>
<section>
<p>메인 페이지 컨텐츠</p>
<div>메인 페이지 포함 내용</div>
</section>
<footer>
레이아웃 푸터
</footer>
</body>
</html>
- 결과적으로 전체 HTML 문서는 layoutFile.html을 기반으로 하고,
- 동적으로 전달된 <title>, <section> 조각이 해당 위치에 삽입되면서 페이지가 구성된다.
정리
- <html> 자체에 th:fragment를 정의함으로써 문서 전체를 레이아웃 조각화할 수 있다.
- th:replace로 페이지 전체를 대체하면서, 필요한 조각들을 파라미터로 전달한다.
- 앞에서 배운 head 조각 전달보다 확장된 형태이며, 기본 템플릿 구조를 고정하고 내용만 바꾸고 싶을 때 유용하다.
- 구조적 일관성과 재사용성 측면에서 매우 효과적인 패턴이다.
'Spring MVC' 카테고리의 다른 글
[MVC] 타임리프 - 스프링 통합과 폼(2) (2) | 2025.06.24 |
---|---|
[MVC] 타임리프 - 스프링 통합과 폼(1) (1) | 2025.06.21 |
[MVC] 타임리프 - 기본 기능(3) (1) | 2025.06.14 |
[MVC] 타임리프 - 기본 기능(2) (1) | 2025.06.09 |
[MVC] 타임리프 - 기본 기능(1) (0) | 2025.06.04 |