thymeleaf layout에서 th:replace 인자 전달을 못 할 경우 처리하기

thymeleaf 레이아웃

타임리프에서 레이아웃을 처리하려면, common.html과 같은 레이아웃 파일을 만들고, 별도 페이지에서 th:replace=”layout/common :: common_header(~{::title}, ~{::link}, ~{::script}, ~{::main})” 와 같은 방식으로 레이아웃을 가져올 수 있다.

common.html

<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" th:fragment="common_header(title, links, scripts, content)">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">

    <!-- 타이틀 -->
    <title th:replace="${title}">title</title>

    <!-- 공통 -->

    <!-- link, script -->
    <th:block th:replace="${links}"></th:block>
    <th:block th:replace="${scripts}"></th:block>

</head>
<body>
<main th:replace="${content}">
    메인 콘텐츠
</main>
</body>
</html>

index.html

<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" th:replace="~{layout/common :: common_header(~{::title}, ~{::link}, ~{::script}, ~{::main})}">
<head>
    <title>온라인 도서관</title>
    <script defer type="module" src="../js/user.js" th:src="@{/js/user.js}"></script>
</head>
<body>
<main>
    <!-- user가 없을 경우에 폼 보여줌 -->
    <div id="login" th:if="${session.user == null}">
        <form method="post" action="login">
            <legend>로그인</legend>
            <input type="text" name="username"/>
            <input type="password" name="password"/>
            <button type="submit">로그인</button>
        </form>
        <a href="join">회원가입</a>
    </div>

    <!-- user가 있을 경우에 인사 -->
    <p th:if="${session.user != null}">안녕하세요, <span th:text="${session.user.username}">username</span>님!</p>

    <!-- books가 있을 경우 목록 보여줌 -->
    <ul th:if="${books != null}">
        <li th:each="book : ${books}">
            <span th:text="${book.name}">책 이름</span>
            <span th:text="${book.isbn}">책 isbn</span>
            <button th:value="${book.id}" onclick="rent(this.value)">대출</button>
            <button th:value="${book.id}" onclick="unRent(this.value)">반납</button>
        </li>
    </ul>
</main>
</body>
</html>

단, link, script는 개별 페이지에서 존재하지 않을 경우가 있다. 위의 인덱스 페이지의 경우에도 link 태그는 별도로 없다. 이럴 경우 어떻게 처리를 해줘야 할까? common.html에서는 한번 더 th:block으로 감싸서 조건부 처리를 해 주고, index.html에서는 ?: null로 null 처리를 해준다.

common.html

<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" th:fragment="common_header(title, links, scripts, content)">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">

    <!-- 타이틀 -->
    <title th:replace="${title}">title</title>

    <!-- 공통 -->

    <!-- link, script -->
    <th:block th:if="${links != null}">
        <th:block th:replace="${links}"/>
    </th:block>
    <th:block th:if="${scripts != null}">
        <th:block th:replace="${scripts}"/>
    </th:block>

</head>
<body>
<main th:replace="${content}">
    메인 콘텐츠
</main>
</body>
</html>

index.html

<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" th:replace="~{layout/common :: common_header(~{::title}, ~{::link ?: null}, ~{::script ?: null}, ~{::main})}">
<head>
    <title>온라인 도서관</title>
    <script defer type="module" src="../js/user.js" th:src="@{/js/user.js}"></script>
</head>
<body>
<main>
    <!-- user가 없을 경우에 폼 보여줌 -->
    <div id="login" th:if="${session.user == null}">
        <form method="post" action="login">
            <legend>로그인</legend>
            <input type="text" name="username"/>
            <input type="password" name="password"/>
            <button type="submit">로그인</button>
        </form>
        <a href="join">회원가입</a>
    </div>

    <!-- user가 있을 경우에 인사 -->
    <p th:if="${session.user != null}">안녕하세요, <span th:text="${session.user.username}">username</span>님!</p>

    <!-- books가 있을 경우 목록 보여줌 -->
    <ul th:if="${books != null}">
        <li th:each="book : ${books}">
            <span th:text="${book.name}">책 이름</span>
            <span th:text="${book.isbn}">책 isbn</span>
            <button th:value="${book.id}" onclick="rent(this.value)">대출</button>
            <button th:value="${book.id}" onclick="unRent(this.value)">반납</button>
        </li>
    </ul>
</main>
</body>
</html>

굳이 블록을 한 번 더 감싸야 할까?

<th:block th:if="${links != null}"
    <th:block th:replace="${links}"/>
</th:block>

위 코드에서 의문이 들 수 있다. 굳이 두 번 감싸지 않고,

<th:block th:if="${links != null}" th:replace="${links}"/>

이렇게 해 주면 안 될까?! 라고 생각했지만, th:replace=””가 if와 함께 평가되기 때문에, 다음과 같은 에러가 발생한다.

org.thymeleaf.exceptions.TemplateInputException은 Thymeleaf가 템플릿이나 프래그먼트를 처리하는 동안 오류가 발생했을 때 던지는 예외이다. 즉, if를 먼저 평가하고 해당하지 않을 경우 다른 th를 평가하지 않는 게 아니므로 문제가 발생한다.

더 좋은 해결 방법이 있다면, 댓글로 남겨주길! 🙏

댓글

개발자  김철준

백엔드 개발자 김철준의 블로그입니다.

주요 프로젝트