타임리프 - 기본 기능

2025. 8. 31. 15:30·Spring

타임리프의 기초에 대해서 이전 포스트에서 다뤄봤다.

특징으로

- 서버 사이드 HTML 렌더링 (SSR) : 백엔드 서버에서 HTML을 동적으로 다룰때 렌더링하는 용도

- 네츄럴 템플릿 : 순수 HTML을 유지할 수 있고 뷰 템플릿을 거치면 동적으로도 변경된 결과 가능. 

- 스프링 통합 지원 등이 있다.

 

텍스트 - text, utext

<span th:text="${data}"> 

- HTML 콘텐츠에 데이터를 출력할 때 위와같이 th:text를 사용하면 된다.

HTML 태그 속성이 아니라 HTML 콘텐츠 영역안에서 직접 데이터를 출력하고 싶으면 [[...]]를 사용 ([[${data}]])

 

Escape

HTML 문서는 <, > 같은 특수문자를 기반으로 정의된다. 따라서 뷰 템플릿으로 HTML 화면을 생성할 땐 특수문자를 조심해야한다.

model.addAttribute("data", "Hello <b>Spring!</b>");

 

개발자의 의도는 Spring을 강조하기 위해 <b>태그를 사용했는데, 화면엔 <b> </b>태그가 그대로 출력된다.

그 이유는 웹 브라우저는 '<'를 태그의 시작으로 인식한다. 따라서 '<'를 태그의 시작이 아닌 문자로 표현할 방법이 필요한데, 이 방법이 HTML 엔티티이다. HTML 엔티티가 <를 문자로 출력해주기 때문에

  • th:text = Hello <b>Spring!</b>

이런식으로 출력이 되는 것이다. 의도대로 Spring을 강조하고 싶다면 untext를 사용하면 된다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>text vs utext</h1>
<ul>
    <li>th:text = <span th:text="${data}"></span></li>
    <li>th:utext = <span th:utext="${data}"></span></li>
</ul>
<h1><span th:inline="none">[[...]] vs [(...)]</span></h1>
<ul>
    <li><span th:inline="none">[[...]] = </span>[[${data}]]</li>
    <li><span th:inline="none">[(...)] = </span>[(${data})]</li>
</ul>
</body>
</html>

 

th:utext, [(..)]를 사용하면 의도대로 Spring이 강조되게 출력할 수 있다.

개발하다보면 escape를 사용하지 않아서 HTML이 깨질때가 있다. escape를 기본으로 쓰고 필요시에만 unescape를 사용해라.

 

 

변수 - SpringEL

 

타임리프에선 변수를 사용할 때 변수 표현식을 사용한다.

변수 표현식: ${...}

그리고 이 변수 표현식에는 스프링 EL이라는 스프링이 제공하는 표현식을 사용할 수 있다.

@GetMapping("/variable")
public String variable(Model model) {
    User userA = new User("userA", 10);
    User userB = new User("userB", 20);

    List<User> list = new ArrayList<>();
    list.add(userA);
    list.add(userB);

    Map<String, User> map = new HashMap<>();
    map.put("userA", userA);
    map.put("userB", userB);

    model.addAttribute("user", userA);
    model.addAttribute("users", list);
    model.addAttribute("userMap", map);

    return "basic/variable";
}

@Data
static class User {
    private String username;
    private int age;

    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }
}

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>SpringEL 표현식</h1>
<ul>Object
    <li>${user.username} = <span th:text="${user.username}"></span></li>
    <li>${user['username']} = <span th:text="${user['username']}"></span></li>
    <li>${user.getUsername()} = <span th:text="${user.getUsername()}"></span></li>
</ul>
<ul>List
    <li>${users[0].username} = <span th:text="${users[0].username}"></span></li>
    <li>${users[0]['username']} = <span th:text="${users[0]['username']}"></span></li>
    <li>${users[0].getUsername()} = <span th:text="${users[0].getUsername()}"></span></li>
</ul>
<ul>Map
    <li>${userMap['userA'].username} = <span th:text="${userMap['userA'].username}"></span></li>
    <li>${userMap['userA']['username']} = <span th:text="${userMap['userA']
['username']}"></span></li>
    <li>${userMap['userA'].getUsername()} = <span th:text="${userMap['userA'].getUsername()}"></span></li>
</ul>
</body>
</html>

 

 

SpringEL의 다양한 표현식 사용법

 

Object

- user.username: user의 username을 프로퍼티로 접근 (user.getUsername())

- user['username']: 위와 같음

- user.getUsername(): user의 getUsername()을 직접 호출

 

List

- user[0].username: List에서 첫번째 회원을 찾고 username 프로퍼티 접근 (list.get(0).getUsername())

- user[0]['username']: 위와 같음

- user[0].getUsername(): List에서 첫번째 회원을 찾고 메서드 직접 호출

 

Map

- userMap['userA'].username: Map에서 key인 userA를 찾고, username 프로퍼티로 접근 (map.get("userA").getUsername())

- userMap['userA']['username]: 위와 같음

- userMap['userA'].getUsername(): Map에서 userA를 찾고 메서드 직접 호출

 

지역 변수 선언

th:with를 사용하면 지역 변수를 선언해서 사용할 수 있다. 당연히 선언한 태그 안에서만 사용할 수 있음.

<h1>지역 변수 - (th:with)</h1>
<div th:with="first=${users[0]}">
    <p>처음 사람의 이름은 <span th:text="${first.username}"></span></p>
</div>

 

users[0]이 first로 치환되어 사용되는 구조이다.

 

기본 객체들

타임리프는 기본 객체들을 제공한다.

- ${#request, response, session, servletContext}들은 스프링 부트 3.0부터 지원하지 않는다.

- ${#locale}

 

스프링 부트 3.0이상 버전은 model에 객체를 추가해서 사용해야 한다.

@GetMapping("/basic-objects")
public String basicObjects(Model model,
                           HttpServletRequest request,
                           HttpServletResponse response,
                           HttpSession session) {
    session.setAttribute("sessionData", "Hello Session");
    model.addAttribute("request", request);
    model.addAttribute("response", response);
    model.addAttribute("servletContext", request.getServletContext());

    return "basic/basic-objects";
}

@Component("helloBean")
static class HelloBean {
    public String hello(String data) {
        return "Hello " + data;
    }
}

 

스프링 부트 3.0이상버전이니 model에 직접 추가한 코드이다.

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>식 기본 객체 (Expression Basic Objects)</h1>
<ul>
    <li>request = <span th:text="${request}"></span></li>
    <li>response = <span th:text="${response}"></span></li>
    <li>session = <span th:text="${session}"></span></li>
    <li>servletContext = <span th:text="${servletContext}"></span></li>
    <li>locale = <span th:text="${#locale}"></span></li>
</ul>
<h1>편의 객체</h1>
<ul>
    <li>Request Parameter = <span th:text="${param.paramData}"></span></li>
    <li>session = <span th:text="${session.sessionData}"></span></li>
    <li>spring bean = <span th:text="${@helloBean.hello('Spring!')}"></span></
    li>
</ul>
</body>
</html>

 

${request}는 HttpServletRequest 객체가 그대로 제공되기 때문에 regeust.getParameter("data")처럼 불편하게 접근해야한다.

객체를 그대로 제공

 

이런 점을 편리하게 해결하기 위해 편의 객체도 제공한다.

- http 요청 파라미터 접근 : param

- http 세션 접근 : session

- 스프링 빈 접근 : @ 

(예시코드는 위 코드 참조)

 

유틸리티 객체와 날짜

@GetMapping("/date")
public String date(Model model) {
    model.addAttribute("localDateTime", LocalDateTime.now());
    return "basic/date";
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>LocalDateTime</h1>
<ul>
    <li>default = <span th:text="${localDateTime}"></span></li>
    <li>yyyy-MM-dd HH:mm:ss = <span th:text="${#temporals.format(localDateTime,
'yyyy-MM-dd HH:mm:ss')}"></span></li>
</ul>
<h1>LocalDateTime - Utils</h1>
<ul>
    <li>${#temporals.day(localDateTime)} = <span th:text="${#temporals.day(localDateTime)}"></span></li>
    <li>${#temporals.month(localDateTime)} = <span th:text="${#temporals.month(localDateTime)}"></span></li>
    <li>${#temporals.monthName(localDateTime)} = <span th:text="${#temporals.monthName(localDateTime)}"></span></li>
    <li>${#temporals.monthNameShort(localDateTime)} = <span th:text="${#temporals.monthNameShort(localDateTime)}"></span></li>
    <li>${#temporals.year(localDateTime)} = <span th:text="${#temporals.year(localDateTime)}"></span></li>
    <li>${#temporals.dayOfWeek(localDateTime)} = <span th:text="${#temporals.dayOfWeek(localDateTime)}"></span></li>
    <li>${#temporals.dayOfWeekName(localDateTime)} = <span th:text="${#temporals.dayOfWeekName(localDateTime)}"></span></li>
    <li>${#temporals.dayOfWeekNameShort(localDateTime)} = <span th:text="${#temporals.dayOfWeekNameShort(localDateTime)}"></span></li>
    <li>${#temporals.hour(localDateTime)} = <span th:text="${#temporals.hour(localDateTime)}"></span></li>
    <li>${#temporals.minute(localDateTime)} = <span th:text="${#temporals.minute(localDateTime)}"></span></li>
    <li>${#temporals.second(localDateTime)} = <span th:text="${#temporals.second(localDateTime)}"></span></li>
    <li>${#temporals.nanosecond(localDateTime)} = <span th:text="${#temporals.nanosecond(localDateTime)}"></span></li>
</ul>
</body>
</html>

 

별거없다. 필요할때 찾아서 쓰면 됨

 

URL 링크

타임리프에서 URL을 생성할 땐 @{...} 문법을 사용하면 된다.

@GetMapping("link")
public String Link(Model model) {
    model.addAttribute("param1", "data1");
    model.addAttribute("param2", "data2");
    return "basic/link";
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>URL 링크</h1>
<ul>
    <li><a th:href="@{/hello}">basic url</a></li>
    <li><a th:href="@{/hello(param1=${param1}, param2=${param2})}">hello query
        param</a></li>
    <li><a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}">path variable</a></li>
    <li><a th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}">path
        variable + query parameter</a></li>
</ul>
</body>
</html>

 

단순한 URL

- @{/hello} -> hello

 

쿼리 파라미터

- @{/hello(param1=${param1}, param2=${param2})} -> /hello?param1=data1&param2=data2

- ()에 있는 부분은 쿼리 파라미터로 처리된다.

 

경로 변수

- @{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})} -> /hello/data1/data2

- URL 경로상에 변수가 있으면 () 부분은 경로변수로 처리된다.

 

경로 변수 + 쿼리 파라미터

- @{/hello/{param1}(param1=${param1}, param2=${param2})} -> /hello/data1?param2=data2

- 경로 변수와 쿼리파라미터를 함께 사용할 수 있다. (param2부터는 쿼리파라미터로 표현됨)

 

리터럴

리터럴은 소스 코드상에 고정된 값을 말하는 용어이다.

String a = "Hello" < Hello는 문자 리터럴

int a = 10 & 20 < 10, 20은 숫자 리터럴

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>리터럴</h1>
<ul>
    <li>'hello' + ' world!' = <span th:text="'hello' + ' world!'"></span></li>
    <li>'hello world!' = <span th:text="'hello world!'"></span></li>
    <li>'hello ' + ${data} = <span th:text="'hello ' + ${data}"></span></li>
    <li>리터럴 대체 |hello ${data}| = <span th:text="|hello ${data}|"></span></li>
</ul>
</body>
</html>

 

타임리프에서 문자 리터럴은 항상 ' 로 감싸야 한다. 하지만 항상 감싸는것은 귀찮으니 공백 없이 쭉 이어진다면 하나의 의미있는 코튼으로 인지해서 다음과 같이 작은 따옴표를 생략할 수 있다.

A-Z, a-z, 0-9, [], ., -, _

 

오류나는 코드

<span th:text="hello world!"></span>

hello 띄고 world!가 있으므로 하나의 의미있는 토큰으로 인식되지 않음

"'hello world!'"로 감싸줘야 인식 가능.

 

연산

자바의 연산과 크게 다르지 않다. 하지만 HTML 엔티티만 조심하면 된다.

<li>1 > 10 = <span th:text="1 &gt; 10"></span></li>
<li>1 gt 10 = <span th:text="1 gt 10"></span></li>
<li>1 >= 10 = <span th:text="1 >= 10"></span></li>
<li>1 ge 10 = <span th:text="1 ge 10"></span></li>
<li>1 == 10 = <span th:text="1 == 10"></span></li>
<li>1 != 10 = <span th:text="1 != 10"></span></li>

 

<, >를 사용할 수 없기떄문에 gt, ge를 사용하면 된다.

elvis 연산자는 ?:로 사용한다 (알고있으니 패스)

No-Operation : ?:를 만족하지 않으면 HTML 기본 내용을 출력한다.

<li>${nullData}?: _ = <span th:text="${nullData}?: _">데이터가 없습니다.</span></li>

 

 

속성 값 설정

타임리프는 주로 HTML 태그에 th:* 속성을 지정하는 방식으로 동작한다. 속성을 적용하면 기존 속성을 대체한다. 없다면 새로 만든다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>속성 설정</h1>
<input type="text" name="mock" th:name="userA" />
<h1>속성 추가</h1>
- th:attrappend = <input type="text" class="text" th:attrappend="class='large'" /><br/>
- th:attrprepend = <input type="text" class="text" th:attrprepend="class='large'" /><br/>
- th:classappend = <input type="text" class="text" th:classappend="large" /><br/>
<h1>checked 처리</h1>
- checked o <input type="checkbox" name="active" th:checked="true" /><br/>
- checked x <input type="checkbox" name="active" th:checked="false" /><br/>
- checked=false <input type="checkbox" name="active" checked="false" /><br/>
</body>
</html>

 

 th:가 있으면 기존 속성을 대체해 mock대신 userA가 들어오게 된다.

mvc1편에서 다뤘던것 처럼 뷰 템플릿을 통할 때 이런 방식이 적용된다.

 

속성 추가

- th:attrappend: 속성 값의 뒤에 값을 추가한다.

- th:attrprepend: 속성 값의 앞에 값을 추가한다.

- th:classappend: class 속성에 자연스럽게 추가한다. (편의 기능)

 

checked 처리

위 코드 checked처리 부분에, th를 쓰지않고 checked속성을 쓰면, true, false 여부와 상관없이 checked 처리가 된다.

타임리프의 th:checked는 값이 false인 경우 checked 속성 자체를 제거한다.

그래서 나중에 컨트롤러에서 데이터를 받아올 때 th:checked="${isChecked}" 이런식으로 활용할 수 있게 된다.

 

 

조건부 평가

타임리프의 조건식은 if, unless(if 반대) 두가지가 있다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>if, unless</h1>
<table border="1">
    <tr>
        <th>count</th>
        <th>username</th>
        <th>age</th>
    </tr>
    <tr th:each="user, userStat : ${users}">
        <td th:text="${userStat.count}">1</td>
        <td th:text="${user.username}">username</td>
        <td>
            <span th:text="${user.age}">0</span>
            <span th:text="'미성년자'" th:if="${user.age lt 20}"></span>
            <span th:text="'미성년자'" th:unless="${user.age ge 20}"></span>
        </td>
    </tr>
</table>
<h1>switch</h1>
<table border="1">
    <tr>
        <th>count</th>
        <th>username</th>
        <th>age</th>
    </tr>
    <tr th:each="user, userStat : ${users}">
        <td th:text="${userStat.count}">1</td>
        <td th:text="${user.username}">username</td>
        <td th:switch="${user.age}">
            <span th:case="10">10살</span>
            <span th:case="20">20살</span>
            <span th:case="*">기타</span>
        </td>
    </tr>
</table>
</body>
</html>

 

if, unless는 해당 조건이 맞지 않으면 태그 자체를 렌더링 하지 않는다. (span부분 자체가 렌더링 되지 않음)

 

switch도 사용할 수 있다.

 

주석

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>예시</h1>
<span th:text="${data}">html data</span>
<h1>1. 표준 HTML 주석</h1>
<!--
<span th:text="${data}">html data</span>
-->
<h1>2. 타임리프 파서 주석</h1>
<!--/* [[${data}]] */-->
<!--/*-->
<span th:text="${data}">html data</span>
<!--*/-->
<h1>3. 타임리프 프로토타입 주석</h1>
<!--/*/
/*/-->
<span th:text="${data}">html data</span>
</body>
</html>

 

1. HTML 표준 주석

- 타임리프가 렌더링 하지 않고 그대로 남겨둠

 

2. 타임리프 파서 주석

- 타임리프의 진짜 주석이다. 렌더링에서 주석 부분을 제거

 

3. 프로토타입 주석

- HTML을 웹 브라우저에서 열었을 경우엔 타임리프가 렌더링하지 않음. 타임리프 렌더링을 거치면 정상적으로 렌더링되어 출력됨

 

블록

<th:block>은 HTML 태그가 아닌 타임리프의 유일한 자체 태그이다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<th:block th:each="user : ${users}">
    <div>
        사용자 이름1 <span th:text="${user.username}"></span>
        사용자 나이1 <span th:text="${user.age}"></span>
    </div>
    <div>
        요약 <span th:text="${user.username} + ' / ' + ${user.age}"></span>
    </div>
</th:block>
</body>
</html>

 

block으로 된 부분들이 같이 출력되는걸 확인할 수 있다.

위 처럼 어쩔수없이 사용하거나 사용하기 애매한 경우에 사용된다. 주로 쓰는 방법은 아니다.

 

자바스크립트 인라인

타임리프는 자바스크립트에서 타임리프를 편리하게 사용할 수 있는 자바스크립트 인라인 기능을 제공한다.

<script th:inline="javascript">

@GetMapping("/javascript")
public String javascript(Model model) {

    model.addAttribute("user", new User("UserA", 10));
    addUsers(model);
    return "basic/javascript";
}

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!-- 자바스크립트 인라인 사용 전 -->
<script>
    var username = [[${user.username}]];
    var age = [[${user.age}]];
    //자바스크립트 내추럴 템플릿
    var username2 = /*[[${user.username}]]*/ "test username";
    //객체
    var user = [[${user}]];
</script>
<!-- 자바스크립트 인라인 사용 후 -->
<script th:inline="javascript">
    var username = [[${user.username}]];
    var age = [[${user.age}]];
    //자바스크립트 내추럴 템플릿
    var username2 = /*[[${user.username}]]*/ "test username";
    //객체
    var user = [[${user}]];
</script>
<!-- 자바스크립트 인라인 each -->
<script th:inline="javascript">
    [# th:each="user, stat : ${users}"]
    var user[[${stat.count}]] = [[${user}]];
    [/]
</script>
</body>
</html>

 

 

인라인을 사용하지 않은 소스와 사용한 소스를 비교해보자

 

텍스트 렌더링 기능

- var username = UserA는 문자열이기 때문에 UserA로 사용되면 오류가 발생한다. age의 경우는 숫자라 ""가 필요 없지만 usernamse은 ""가 필요하다. 이러한 부분을 인라인이 처리해준다.

추가로 문제가 될 수 있는 문자가 포함되어 있으면 escape처리도 해준다.

 

자바스크립트 내추럴 템플릿

- 인라인 사용 전 결과를 보면 그대로 해석을 해버렸기 때문에 내추럴 템플릿 기능이 동작하지 않고, 렌더링 내용이 주석처리 되어버렸다.

인라인을 사용 한 후엔 주석부분이 제거되고 의도대로 "UserA"가 정확히 적용 된다.

 

객체

- 자바스크립트 인라인 기능을 사용하면 객체를 JSON으로 자동으로 변환해준다. 

인라인 사용 전은 toString이 호출된 값이고, 사용 후엔 JSON으로 변환된 객체를 보여준다.

 

자바스크립트 인라인 each

- each를 지원한다. 위의 코드처럼 사용할 수 있다.

 

 

템플릿 조각

웹 페이지를 개발할 때는 공통 영역이 많이 있다. 상단 영역이나 하단, 좌측 카테고리 등 여러 페이지에서 함께 사용하는 영역들이 있다. 이런 부분을 코드로 복사해서 사용하면 변경시 여러 페이지를 다 변경해야하기 때문에 비효율적이다. 이런 문제를 해결하기 위해 템플릿 조각과 레이아웃 기능을 지원한다.

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<footer th:fragment="copy">
    푸터 자리 입니다.
</footer>
<footer th:fragment="copyParam (param1, param2)">
    <p>파라미터 자리 입니다.</p>
    <p th:text="${param1}"></p>
    <p th:text="${param2}"></p>
</footer>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>부분 포함</h1>
<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>
</body>
</html>

 

th:fragment가 있는 태그는 다른곳에 포함되는 코드 조각으로 이해하면 된다.

(그래서 fragmentMain.html에서 푸터를 호출할 수 있었음)

 

부분 포함 insert

- th:insert를 사용하면 현재 태그 (div) 내부에 추가한다.

 

부분 포함 replace

- th:replace를 사용하면 현재 태그 (div)를 대체한다.

 

부분 포함 단순 표현식

- ~{...}를 사용하는것이 원칙이지만 코드가 단순하면 생략할 수 있다.

 

파라미터 사용

- 파라미터를 전달해서 동적으로 조각을 렌더링 할 수도 있다.

 

 

템플릿 레이아웃1

이전엔 일부 코드 조각을 가지고 와서 사용했다면, 이번에는 개념을 더 확장해서 코드 조각을 레이아웃에 넘겨서 사용하는 방법에 대해 알아보자

 

<head>에 공통으로 사용하는 css, javascript 같은 정보들이 있는데, 이런 공통 정보들을 한곳에 모아두고, 공통으로 사용하지만, 각 페이지마다 필요한 정보를 더 추가해서 사용하고 싶다면 다음과 같은 방법으로 사용하면 된다.

<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>

 

레이아웃의 베이스가 되는 코드

<!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>

 

레이아웃에 추가적인 요소를 적용시킨 코드

 

레이아웃 타이틀을 -> 메인 타이틀로 변경하고, 추가로 링크 태그들을 전달한것을 확인할 수 있다.

 

common_header(~{::title},~{::link})이 부분이 핵심이다.

::title은 현재 페이지의 title 태그들을 전달

::link는 현재 페이지의 link 태그들을 전달한다.

 

 

템플릿 레이아웃2

 

템플릿 레이아웃을 확장시켜보자

앞서 이야기 한 개념들을 <head>가 아닌 <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>

layoutFile.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로 html 자체를 layoutFile.html로 변경했다. layoutFile.html엔 th:fragment 속성이 정의되어 있다.

이 레이아웃 파일을 기본으로 하고 여기에 필요한 내용을 전달해서 부분부분 변경할 수 있다.

 

규모가 작다면 fragment로 조각조각을 넣어서 구현할 수 있지만, 규모가 크거나 할 경우에 모든 웹 페이지를 하나하나 변경할 수 없는 노릇이니 레이아웃을 사용하는 것이다.

'Spring' 카테고리의 다른 글

메세지, 국제화  (1) 2025.09.07
타임리프 - 스프링 통합과 폼  (1) 2025.09.06
상품 도메인 개발 - 뷰 템플릿, 타임리프  (1) 2025.08.28
스프링 MVC - 기본 기능  (1) 2025.08.25
Spring MVC - 구조 이해  (0) 2025.06.19
'Spring' 카테고리의 다른 글
  • 메세지, 국제화
  • 타임리프 - 스프링 통합과 폼
  • 상품 도메인 개발 - 뷰 템플릿, 타임리프
  • 스프링 MVC - 기본 기능
공부처음하는사람
공부처음하는사람
  • 공부처음하는사람
    lazzzykim
    공부처음하는사람
  • 전체
    오늘
    어제
    • 분류 전체보기 (159)
      • Kotlin (31)
      • Java (56)
      • Spring (44)
      • JPA (6)
      • Algorithm (3)
      • TroubleShooting (1)
      • 내일배움캠프 프로젝트 (14)
      • Setting (2)
      • ... (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 인기 글

  • 태그

    spring
    Di
    김영한의 실전자바
    김영한
    싱글톤
    김영한의 실전 자바
    java
    제네릭
    다형성
    빈 생명주기
    OCP
    내일배움캠프
    캡슐화
    중첩클래스
    예외처리
    배열
    언체크예외
    트랜잭션
    jpa
    kotlin
  • hELLO· Designed By정상우.v4.10.3
공부처음하는사람
타임리프 - 기본 기능
상단으로

티스토리툴바