하이어코딩 RSS 태그 관리 글쓰기 방명록 mahiru
2025-07-21 19:39:13
728x90
반응형

🔥 들어가며

"TemplateInputException", "SpelEvaluationException", "Error resolving template"...

Thymeleaf를 사용하다 보면 마주치는 수많은 오류들. 빨간 에러 페이지를 보면 막막하지만, 사실 각 오류는 명확한 원인과 해결책을 가지고 있습니다.

이 글에서는 Thymeleaf 개발 중 자주 발생하는 오류들을 체계적으로 정리하고, 실제 사례와 함께 해결 방법을 제시합니다. 더 이상 Thymeleaf 오류가 두렵지 않게 될 것입니다!

📑 목차

  1. 템플릿 파싱 오류
  2. 표현식 평가 오류
  3. 레이아웃 관련 오류
  4. Fragment 오류
  5. 반복문과 조건문 오류
  6. 날짜/시간 처리 오류
  7. 보안 관련 오류
  8. 디버깅 전략

템플릿 파싱 오류

1. TemplateInputException: Template Not Found

오류 메시지

 
org.thymeleaf.exceptions.TemplateInputException: 
Error resolving template [users/list], template might not exist or might not be accessible by any of the configured Template Resolvers

원인

  • 템플릿 파일 경로 오류
  • 파일명 오타
  • 잘못된 디렉토리 구조

해결 방법

파일 구조 확인

 
src/main/resources/
└── templates/
    ├── users/
    │   ├── list.html    ✅ 올바른 위치
    │   └── form.html
    └── index.html

Controller 반환값 확인

 
java
@Controller
public class UserController {
    
    @GetMapping("/users")
    public String listUsers(Model model) {
        // ❌ 잘못된 방법
        return "users/list.html";  // .html 확장자 포함
        return "/users/list";      // 앞에 슬래시
        
        // ✅ 올바른 방법
        return "users/list";       // templates/ 기준 상대 경로
    }
}

application.properties 설정

 
properties
# 기본 설정 (보통 수정 불필요)
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

# 캐시 비활성화 (개발 환경)
spring.thymeleaf.cache=false

2. 닫히지 않은 태그 오류

오류 메시지

 
org.xml.sax.SAXParseException: The element type "input" must be terminated by the matching end-tag "</input>"

원인

  • HTML5가 아닌 XML 모드에서 self-closing 태그 미사용
  • 태그 중첩 오류

해결 방법

HTML5 모드 사용

 
properties
# application.properties
spring.thymeleaf.mode=HTML

올바른 태그 사용

 
html
<!-- ❌ 잘못된 방법 (XML 모드) -->
<input type="text" name="username">
<br>
<img src="logo.png">

<!-- ✅ 올바른 방법 1: Self-closing -->
<input type="text" name="username" />
<br />
<img src="logo.png" />

<!-- ✅ 올바른 방법 2: HTML5 모드 사용 -->
<input type="text" name="username">
<br>
<img src="logo.png">

3. 잘못된 속성 구문

오류 메시지

 
org.thymeleaf.exceptions.TemplateProcessingException: 
Could not parse as expression: "${user.name"

원인

  • 따옴표 누락 또는 불일치
  • 중괄호 누락

해결 방법

 
html
<!-- ❌ 잘못된 방법 -->
<p th:text=${user.name}>Name</p>              <!-- 따옴표 누락 -->
<p th:text="${user.name>Name</p>              <!-- 닫는 중괄호 누락 -->
<p th:text='${user.name}'>Name</p>            <!-- 작은따옴표 사용 -->

<!-- ✅ 올바른 방법 -->
<p th:text="${user.name}">Name</p>

<!-- 따옴표 안에 따옴표가 필요한 경우 -->
<p th:text="|User's name: ${user.name}|">Name</p>

표현식 평가 오류

1. SpelEvaluationException: Null Pointer

오류 메시지

 
org.springframework.expression.spel.SpelEvaluationException: 
EL1007E: Property or field 'name' cannot be found on null

원인

  • null 객체의 속성 접근
  • Controller에서 모델 추가 누락

해결 방법

안전한 네비게이션 연산자 사용

 
html
<!-- ❌ 위험한 방법 -->
<p th:text="${user.name}">Name</p>

<!-- ✅ 안전한 방법 1: 조건부 렌더링 -->
<p th:if="${user != null}" th:text="${user.name}">Name</p>

<!-- ✅ 안전한 방법 2: 안전 연산자 -->
<p th:text="${user?.name}">Name</p>

<!-- ✅ 안전한 방법 3: 기본값 제공 -->
<p th:text="${user?.name ?: 'Anonymous'}">Name</p>

<!-- ✅ 안전한 방법 4: Elvis 연산자 -->
<p th:text="${user?.name} ?: 'No name'">Name</p>

중첩 객체 안전 접근

 
html
<!-- ❌ 위험 -->
<p th:text="${user.address.city}">City</p>

<!-- ✅ 안전 -->
<p th:text="${user?.address?.city ?: 'Unknown'}">City</p>

<!-- 더 복잡한 경우 -->
<div th:object="${user}">
    <p th:text="*{address?.city}">City</p>
    <p th:text="*{address?.street}">Street</p>
</div>

2. 타입 변환 오류

오류 메시지

 
org.springframework.expression.spel.SpelEvaluationException: 
EL1030E: The operator 'ADD' is not supported between objects of type 'java.lang.String' and 'java.lang.Integer'

원인

  • 잘못된 타입 간 연산
  • 문자열과 숫자 혼용

해결 방법

 
html
<!-- ❌ 잘못된 방법 -->
<p th:text="${'Total: ' + order.amount}">Total</p>

<!-- ✅ 올바른 방법 1: 문자열 연결 -->
<p th:text="|Total: ${order.amount}|">Total</p>

<!-- ✅ 올바른 방법 2: 연결 연산자 -->
<p th:text="${'Total: ' + #numbers.formatDecimal(order.amount, 1, 2)}">Total</p>

<!-- 숫자 연산 -->
<p th:text="${order.quantity * order.price}">Total Price</p>

<!-- 타입 변환 -->
<p th:text="${#strings.toString(order.quantity)}">Quantity</p>

3. 컬렉션 접근 오류

오류 메시지

 
org.springframework.expression.spel.SpelEvaluationException: 
EL1025E: The collection has '0' elements, index '0' is invalid

원인

  • 빈 컬렉션 접근
  • 잘못된 인덱스

해결 방법

 
html
<!-- ❌ 위험한 방법 -->
<p th:text="${users[0].name}">First User</p>

<!-- ✅ 안전한 방법 1: 크기 확인 -->
<p th:if="${!users.isEmpty()}" th:text="${users[0].name}">First User</p>

<!-- ✅ 안전한 방법 2: 안전 연산자 -->
<p th:text="${users[0]?.name ?: 'No users'}">First User</p>

<!-- ✅ 안전한 방법 3: 조건부 표현 -->
<p th:text="${#lists.size(users) > 0 ? users[0].name : 'Empty'}">First User</p>

<!-- 반복문 사용 -->
<div th:each="user, iterStat : ${users}">
    <p th:if="${iterStat.first}" th:text="${user.name}">First User</p>
</div>

레이아웃 관련 오류

1. Fragment Expression 오류

오류 메시지

 
org.thymeleaf.exceptions.TemplateInputException: 
Error resolving fragment: "${content}": template or fragment could not be resolved

원인

  • Thymeleaf Layout Dialect 의존성 누락
  • 잘못된 fragment 구문

해결 방법

의존성 추가

 
xml
<!-- pom.xml -->
<dependency>
    <groupId>nz.net.ultraq.thymeleaf</groupId>
    <artifactId>thymeleaf-layout-dialect</artifactId>
    <version>3.3.0</version>
</dependency>

레이아웃 파일 (layout/default.html)

 
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
    <title layout:title-pattern="$CONTENT_TITLE - $LAYOUT_TITLE">My Site</title>
</head>
<body>
    <header>
        <nav><!-- Navigation --></nav>
    </header>
    
    <!-- 콘텐츠가 삽입될 위치 -->
    <main layout:fragment="content">
        Default content
    </main>
    
    <footer>
        <p>&copy; 2024 My Site</p>
    </footer>
</body>
</html>

페이지 파일 (users/list.html)

 
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layout/default}">
<head>
    <title>User List</title>
</head>
<body>
    <div layout:fragment="content">
        <h1>Users</h1>
        <!-- 페이지 콘텐츠 -->
    </div>
</body>
</html>

2. 순환 참조 오류

오류 메시지

 
org.thymeleaf.exceptions.TemplateProcessingException: 
Circular template reference detected: "fragments/header" -> "layout/main" -> "fragments/header"

원인

  • 템플릿 간 순환 참조
  • 잘못된 include 구조

해결 방법

 
html
<!-- ❌ 순환 참조 발생 -->
<!-- layout/main.html -->
<div th:replace="~{fragments/header :: header}"></div>

<!-- fragments/header.html -->
<div th:fragment="header" th:replace="~{layout/main :: content}">
    <!-- 순환 참조! -->
</div>

<!-- ✅ 올바른 구조 -->
<!-- layout/main.html -->
<body>
    <div th:replace="~{fragments/header :: header}"></div>
    <div layout:fragment="content"></div>
    <div th:replace="~{fragments/footer :: footer}"></div>
</body>

<!-- fragments/header.html -->
<div th:fragment="header">
    <header>
        <!-- 헤더 내용만 포함 -->
    </header>
</div>

Fragment 오류

1. Fragment Not Found

오류 메시지

 
org.thymeleaf.exceptions.TemplateProcessingException: 
Fragment "header" was not found in template "fragments/common"

원인

  • Fragment 이름 오타
  • Fragment 정의 누락

해결 방법

Fragment 정의 (fragments/common.html)

 
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
    <!-- Fragment 정의 -->
    <div th:fragment="header">
        <header>
            <h1>My Website</h1>
        </header>
    </div>
    
    <!-- 파라미터를 받는 Fragment -->
    <div th:fragment="alert(type, message)">
        <div th:class="|alert alert-${type}|" th:text="${message}">
            Alert message
        </div>
    </div>
    
    <!-- 동적 Fragment -->
    <div th:fragment="dynamic(content)">
        <div class="container" th:utext="${content}">
            Dynamic content
        </div>
    </div>
</body>
</html>

Fragment 사용

 
html
<!-- ✅ 올바른 사용법 -->
<!-- replace: 전체 태그 교체 -->
<div th:replace="~{fragments/common :: header}"></div>

<!-- insert: 태그 내부에 삽입 -->
<div th:insert="~{fragments/common :: header}"></div>

<!-- include: 내용만 포함 (deprecated) -->
<div th:include="~{fragments/common :: header}"></div>

<!-- 파라미터 전달 -->
<div th:replace="~{fragments/common :: alert('success', 'Operation completed!')}"></div>

<!-- 조건부 Fragment -->
<div th:replace="${user.isAdmin()} ? ~{fragments/admin :: menu} : ~{fragments/user :: menu}"></div>

2. Fragment 표현식 오류

오류 메시지

 
org.thymeleaf.exceptions.TemplateProcessingException: 
Exception evaluating SpringEL expression: "fragments/common :: ${fragmentName}"

원인

  • 동적 Fragment 이름 사용 시 구문 오류

해결 방법

 
html
<!-- ❌ 잘못된 방법 -->
<div th:replace="~{fragments/common :: ${fragmentName}}"></div>

<!-- ✅ 올바른 방법 1: 전처리 -->
<div th:replace="~{fragments/common :: __${fragmentName}__}"></div>

<!-- ✅ 올바른 방법 2: 조건부 선택 -->
<div th:replace="${condition} ? ~{fragments/common :: fragment1} : ~{fragments/common :: fragment2}"></div>

<!-- ✅ 올바른 방법 3: Fragment 매개변수 -->
<div th:replace="~{fragments/common :: dynamic(${content})}"></div>

반복문과 조건문 오류

1. th:each 오류

오류 메시지

 
org.thymeleaf.exceptions.TemplateProcessingException: 
Exception evaluating SpringEL expression: "user.name" (template: "users/list" - line 10, col 15)

원인

  • 반복 변수 스코프 오류
  • null 컬렉션 반복

해결 방법

 
html
<!-- ❌ 잘못된 방법 -->
<tr th:each="users : ${user}">  <!-- 변수명 혼동 -->
    <td th:text="${users.name}"></td>
</tr>

<!-- ✅ 올바른 방법 -->
<tr th:each="user : ${users}">
    <td th:text="${user.name}"></td>
</tr>

<!-- null 안전 처리 -->
<tbody th:if="${users != null}">
    <tr th:each="user : ${users}">
        <td th:text="${user.name}"></td>
    </tr>
</tbody>
<tbody th:unless="${users != null}">
    <tr>
        <td colspan="3">No users found</td>
    </tr>
</tbody>

<!-- 상태 변수 사용 -->
<tr th:each="user, iterStat : ${users}" 
    th:class="${iterStat.odd} ? 'odd' : 'even'">
    <td th:text="${iterStat.count}">1</td>
    <td th:text="${user.name}">Name</td>
    <td th:text="${iterStat.first} ? 'FIRST' : ''"></td>
    <td th:text="${iterStat.last} ? 'LAST' : ''"></td>
</tr>

2. 중첩 반복문 오류

원인

  • 변수 이름 충돌
  • 스코프 혼동

해결 방법

 
html
<!-- ❌ 문제가 될 수 있는 방법 -->
<div th:each="category : ${categories}">
    <div th:each="item : ${items}">
        <!-- 어느 스코프의 item인지 불명확 -->
    </div>
</div>

<!-- ✅ 명확한 방법 -->
<div th:each="category : ${categories}">
    <h3 th:text="${category.name}">Category</h3>
    <ul>
        <li th:each="product : ${category.products}">
            <span th:text="${product.name}">Product</span>
            <span th:text="${category.name}">Still accessible</span>
        </li>
    </ul>
</div>

<!-- 인덱스 활용 -->
<table>
    <tr th:each="row, rowStat : ${matrix}">
        <td th:each="cell, colStat : ${row}"
            th:text="|[${rowStat.index},${colStat.index}] = ${cell}|">
            Cell
        </td>
    </tr>
</table>

3. 조건문 오류

오류 메시지

 
org.thymeleaf.exceptions.TemplateProcessingException: 
Cannot execute subtraction: operands are "null" and "1"

원인

  • 조건문에서 null 처리 누락
  • 잘못된 비교 연산

해결 방법

 
html
<!-- ❌ 위험한 조건 -->
<div th:if="${user.age > 18}">Adult</div>

<!-- ✅ 안전한 조건 1 -->
<div th:if="${user != null and user.age != null and user.age > 18}">Adult</div>

<!-- ✅ 안전한 조건 2 -->
<div th:if="${user?.age != null and user.age > 18}">Adult</div>

<!-- 복잡한 조건 -->
<div th:switch="${user?.status}">
    <p th:case="'ACTIVE'" class="text-success">Active</p>
    <p th:case="'INACTIVE'" class="text-warning">Inactive</p>
    <p th:case="'BANNED'" class="text-danger">Banned</p>
    <p th:case="*" class="text-muted">Unknown</p>
</div>

<!-- th:unless 사용 -->
<div th:unless="${#lists.isEmpty(errors)}">
    <ul>
        <li th:each="error : ${errors}" th:text="${error}"></li>
    </ul>
</div>

날짜/시간 처리 오류

1. 날짜 포맷 오류

오류 메시지

 
org.thymeleaf.exceptions.TemplateProcessingException: 
Exception evaluating SpringEL expression: "#dates.format(user.createdAt, 'yyyy-MM-dd')"

원인

  • null 날짜 값
  • 잘못된 날짜 타입
  • Java 8 시간 API 사용

해결 방법

 
html
<!-- ❌ 잘못된 방법 (Java 8+ LocalDateTime에는 #dates 사용 불가) -->
<span th:text="${#dates.format(user.createdAt, 'yyyy-MM-dd')}"></span>

<!-- ✅ Java 8 시간 API 사용 -->
<!-- LocalDateTime -->
<span th:text="${#temporals.format(user.createdAt, 'yyyy-MM-dd HH:mm:ss')}"></span>

<!-- LocalDate -->
<span th:text="${#temporals.format(user.birthDate, 'yyyy-MM-dd')}"></span>

<!-- null 안전 처리 -->
<span th:text="${user.createdAt != null ? #temporals.format(user.createdAt, 'yyyy-MM-dd') : 'N/A'}"></span>

<!-- 다양한 포맷 -->
<span th:text="${#temporals.format(user.createdAt, 'EEEE, MMMM dd, yyyy')}"></span>
<span th:text="${#temporals.format(user.createdAt, 'dd/MM/yyyy HH:mm')}"></span>

<!-- 상대 시간 (커스텀 유틸리티 필요) -->
<span th:text="${@dateUtils.getRelativeTime(user.createdAt)}"></span>

2. 타임존 오류

원인

  • 서버와 클라이언트 타임존 불일치
  • 타임존 변환 누락

해결 방법

 
java
// 커스텀 유틸리티 빈
@Component("dateUtils")
public class DateUtils {
    
    public String formatWithTimezone(LocalDateTime dateTime, String timezone) {
        if (dateTime == null) return "";
        
        ZonedDateTime zdt = dateTime.atZone(ZoneId.systemDefault())
            .withZoneSameInstant(ZoneId.of(timezone));
        
        return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z")
            .format(zdt);
    }
    
    public String getRelativeTime(LocalDateTime dateTime) {
        if (dateTime == null) return "";
        
        LocalDateTime now = LocalDateTime.now();
        long minutes = ChronoUnit.MINUTES.between(dateTime, now);
        
        if (minutes < 1) return "방금 전";
        if (minutes < 60) return minutes + "분 전";
        if (minutes < 1440) return (minutes / 60) + "시간 전";
        return (minutes / 1440) + "일 전";
    }
}
 
html
<!-- 사용 예 -->
<span th:text="${@dateUtils.formatWithTimezone(user.createdAt, 'Asia/Seoul')}"></span>
<span th:text="${@dateUtils.getRelativeTime(post.createdAt)}"></span>

보안 관련 오류

1. Spring Security 통합 오류

오류 메시지

 
org.thymeleaf.exceptions.TemplateProcessingException: 
Exception evaluating SpringEL expression: "#authorization.expression('hasRole(''ROLE_ADMIN'')')"

원인

  • Spring Security Dialect 누락
  • 잘못된 권한 표현식

해결 방법

의존성 추가

 
xml
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>

올바른 사용법

 
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body>
    <!-- 인증 확인 -->
    <div sec:authorize="isAuthenticated()">
        Welcome, <span sec:authentication="name">User</span>!
    </div>
    
    <!-- 권한 확인 -->
    <div sec:authorize="hasRole('ADMIN')">
        <a href="/admin">Admin Panel</a>
    </div>
    
    <!-- 복잡한 권한 표현식 -->
    <div sec:authorize="hasRole('USER') and hasAuthority('WRITE_PRIVILEGE')">
        <button>Create Post</button>
    </div>
    
    <!-- 인증 정보 접근 -->
    <p>Username: <span sec:authentication="principal.username"></span></p>
    <p>Authorities: <span sec:authentication="principal.authorities"></span></p>
    
    <!-- CSRF 토큰 -->
    <meta name="_csrf" th:content="${_csrf.token}"/>
    <meta name="_csrf_header" th:content="${_csrf.headerName}"/>
</body>
</html>

2. XSS 방지 오류

원인

  • 안전하지 않은 HTML 출력
  • 잘못된 이스케이핑

해결 방법

 
html
<!-- ❌ 위험: XSS 취약점 -->
<div th:utext="${userInput}"></div>

<!-- ✅ 안전: 자동 이스케이핑 -->
<div th:text="${userInput}"></div>

<!-- HTML이 필요한 경우: 서버에서 검증 -->
<div th:utext="${@htmlSanitizer.sanitize(userContent)}"></div>

<!-- JavaScript 내 데이터 -->
<script th:inline="javascript">
    /* ✅ 자동 이스케이핑 */
    var username = [[${user.name}]];
    
    /* ❌ 위험: 이스케이핑 없음 */
    var data = [(${userData})];
</script>

<!-- URL 파라미터 -->
<a th:href="@{/users(name=${userName})}">Link</a>

디버깅 전략

1. 디버그 모드 활성화

 
properties
# application.properties
spring.thymeleaf.cache=false
logging.level.org.thymeleaf=DEBUG
logging.level.org.thymeleaf.TemplateEngine.CONFIG=TRACE

2. 유틸리티 객체 활용

 
html
<!-- 객체 정보 확인 -->
<div th:text="${#objects.nullSafe(user, 'NULL')}"></div>
<div th:text="${user.class.name}"></div>

<!-- 컬렉션 정보 -->
<p>Size: <span th:text="${#lists.size(users)}"></span></p>
<p>Empty: <span th:text="${#lists.isEmpty(users)}"></span></p>

<!-- 디버그 정보 출력 -->
<div th:if="${@environment.acceptsProfiles('dev')}">
    <h3>Debug Info</h3>
    <pre th:text="${#objects.toString(user)}"></pre>
    <pre th:text="${#vars}"></pre>
</div>

<!-- 조건부 디버깅 -->
<th:block th:if="${@environment.getProperty('debug.enabled') == 'true'}">
    <div class="debug-panel">
        <h4>Model Attributes</h4>
        <dl th:each="attr : ${#vars.getVariableNames()}">
            <dt th:text="${attr}">name</dt>
            <dd th:text="${#vars.getVariable(attr)}">value</dd>
        </dl>
    </div>
</th:block>

3. 커스텀 에러 페이지

 
html
<!-- templates/error/404.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Page Not Found</title>
</head>
<body>
    <h1>404 - Page Not Found</h1>
    <p>The requested page could not be found.</p>
    
    <div th:if="${@environment.acceptsProfiles('dev')}">
        <h3>Debug Information</h3>
        <p>Timestamp: <span th:text="${timestamp}"></span></p>
        <p>Path: <span th:text="${path}"></span></p>
        <p>Error: <span th:text="${error}"></span></p>
        <p>Message: <span th:text="${message}"></span></p>
    </div>
</body>
</html>

4. 커스텀 Dialect 생성

 
java
@Component
public class CustomDialect extends AbstractProcessorDialect {
    
    public CustomDialect() {
        super("Custom Dialect", "custom", 1000);
    }
    
    @Override
    public Set<IProcessor> getProcessors(String dialectPrefix) {
        Set<IProcessor> processors = new HashSet<>();
        processors.add(new DebugAttributeTagProcessor(dialectPrefix));
        return processors;
    }
}

public class DebugAttributeTagProcessor extends AbstractAttributeTagProcessor {
    
    private static final String ATTR_NAME = "debug";
    
    public DebugAttributeTagProcessor(String dialectPrefix) {
        super(
            TemplateMode.HTML,
            dialectPrefix,
            null,
            false,
            ATTR_NAME,
            true,
            1000,
            true
        );
    }
    
    @Override
    protected void doProcess(
            ITemplateContext context,
            IProcessableElementTag tag,
            AttributeName attributeName,
            String attributeValue,
            IElementTagStructureHandler structureHandler) {
        
        if ("true".equals(attributeValue)) {
            String debug = String.format(
                "<!-- Debug: %s = %s -->",
                tag.getElementCompleteName(),
                context.getVariable(tag.getElementCompleteName())
            );
            structureHandler.insertImmediatelyBefore(debug, false);
        }
    }
}

사용:

 
html
<div custom:debug="true" th:text="${user.name}">Name</div>

성능 최적화 팁

1. Fragment 캐싱

 
html
<!-- 캐시 가능한 Fragment -->
<div th:fragment="expensive-calculation" th:cache="true" th:cache-ttl="3600">
    <!-- 비용이 큰 연산 결과 -->
</div>

2. 지연 로딩

 
html
<!-- 조건부 Fragment 로딩 -->
<div th:if="${showDetails}">
    <div th:replace="~{fragments/user-details :: details(${user})}"></div>
</div>

<!-- AJAX 로딩 준비 -->
<div id="user-details" data-user-id="${user.id}" data-load-on-demand="true">
    <button onclick="loadUserDetails()">Load Details</button>
</div>

3. 프리컴파일

 
java
@Configuration
public class ThymeleafConfig {
    
    @Bean
    public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
        SpringTemplateEngine engine = new SpringTemplateEngine();
        engine.setTemplateResolver(templateResolver);
        engine.setEnableSpringELCompiler(true); // SpEL 컴파일러 활성화
        return engine;
    }
}

마무리

Thymeleaf 오류는 처음에는 당황스럽지만, 패턴을 이해하면 빠르게 해결할 수 있습니다. 이 가이드를 참고하여 더 안정적이고 유지보수하기 쉬운 템플릿을 작성하시기 바랍니다.

핵심 체크리스트

  • ✅ null 안전 연산자 사용 (?.)
  • ✅ Fragment 경로 정확히 지정
  • ✅ 타입 안전성 확인
  • ✅ 보안 취약점 방지 (th:text 사용)
  • ✅ 적절한 오류 처리
  • ✅ 개발 환경에서 캐시 비활성화

디버깅 순서

  1. 브라우저 개발자 도구 확인
  2. 서버 로그 확인
  3. Thymeleaf 디버그 모드 활성화
  4. 단순한 템플릿으로 테스트
  5. 단계별 문제 격리

참고 자료


태그: #Thymeleaf #SpringBoot #ErrorHandling #WebDevelopment #Debugging

728x90
반응형
이 페이지는 리디주식회사에서 제공한 리디바탕 글꼴이 사용되어 있습니다.