Thymeleaf 가이드
기본 문법 요약
| 속성 | 설명 | 예시 |
th:text | 텍스트 출력 (HTML 이스케이프) | th:text="${user.name}" |
th:href | 링크 URL 바인딩 | th:href="@{/path}" |
th:if | 조건부 렌더링 | th:if="${condition}" |
th:unless | 반전 조건 렌더링 | th:unless="${user.admin}" |
th:each | 반복 렌더링 | th:each="item : ${list}" |
th:replace | 태그를 fragment로 교체 | th:replace="~{fragments/header :: header}" |
th:insert | 태그 안에 fragment 삽입 | th:insert="~{fragments/header :: header}" |
th:fragment | fragment 정의 | th:fragment="header" |
th:class | 클래스 바인딩 | th:class="${active} ? 'active' : ''" |
th:utext | HTML 비이스케이프 출력 | th:utext="${doc.htmlContent}" |
표현식
변수 표현식
<!-- ${variable} 형식으로 모델 변수 접근 -->
<span th:text="${user.name}"></span>
<span th:text="${#strings.abbreviate(content, 100)}"></span>
링크 표현식
<!-- @{/path} 형식으로 URL 생성 -->
<a th:href="@{/guide}">홈</a>
<a th:href="@{/guide/{section}/{page}(section=${item.sectionKey}, page=${item.pageKey})}">페이지</a>
<a th:href="@{/list(page=${page}, category=${category})}">목록</a>
<!-- 정적 리소스 -->
<link rel="stylesheet" th:href="@{/css/guide.css}">
<script th:src="@{/js/guide.js}"></script>
조건문
<!-- th:if: 조건이 true이면 렌더링 -->
<div th:if="${doc != null}">...</div>
<div th:if="${list != null and !list.isEmpty()}">...</div>
<!-- th:unless: 조건이 false이면 렌더링 -->
<div th:unless="${user.admin}">일반 사용자 전용</div>
반복문
<!-- th:each: 리스트 반복 -->
<li th:each="item : ${list}" th:text="${item.title}"></li>
<!-- stat 객체로 인덱스 접근 -->
<li th:each="item, stat : ${list}" th:text="${stat.index + 1} + '. ' + ${item.title}"></li>
Fragment (조각) 사용법
Fragment 정의
<!-- templates/fragments/header.html -->
<header th:fragment="header" class="site-header">
<nav>...</nav>
</header>
th:replace로 조각 삽입 (권장)
<!-- 태그 자체를 fragment로 교체 -->
<th:block th:replace="~{fragments/header :: header}"></th:block>
th:insert로 조각 삽입
<!-- 태그 안에 fragment 삽입 -->
<div th:insert="~{fragments/header :: header}"></div>
파라미터 전달
<!-- 파라미터 있는 fragment 호출 -->
<th:block th:replace="~{fragments/alert :: alert('success', '저장되었습니다.')}"></th:block>
<!-- fragment 정의 -->
<div th:fragment="alert(type, message)"
th:classappend="'alert-' + ${type}"
class="alert">
<span th:text="${message}"></span>
</div>
데이터 바인딩
<!-- 올바른 예 — th:text로 자동 이스케이프 -->
<span th:text="${user.name}"></span>
<!-- 신뢰된 서버 생성 HTML — th:utext 허용 (주석 필수) -->
<!-- 서버에서 렌더링된 마크다운 HTML, 사용자 입력 아님 -->
<div th:utext="${doc.htmlContent}"></div>
<!-- 속성 조건부 추가 -->
<a th:class="${item.slug == currentSlug} ? 'nav-link active' : 'nav-link'">링크</a>
<button th:disabled="${!canEdit}">수정</button>
th:utext 사용 기준
| 상황 | 사용 여부 |
| 사용자 입력 데이터 | 금지 |
| 관리자가 등록한 내부 콘텐츠 | 허용 (주석 필수) |
| 서버에서 생성한 HTML (마크다운 변환 등) | 허용 |
JavaScript에 서버 데이터 전달
<!-- 인라인 표현식 사용 (주석 처리로 IDE 오류 방지) -->
<script>
window.GUIDE_CONFIG = {
currentSlug: /*[[${currentSlug}]]*/ '',
pageTitle: /*[[${pageTitle}]]*/ '',
};
</script>
Thymeleaf 유틸리티
<!-- 날짜 포맷 -->
<span th:text="${#temporals.format(item.createdAt, 'yyyy-MM-dd')}"></span>
<span th:text="${#temporals.format(item.createdAt, 'yyyy년 MM월 dd일')}"></span>
<!-- 문자열 유틸 -->
<span th:text="${#strings.isEmpty(value) ? '없음' : value}"></span>
<span th:text="${#strings.abbreviate(title, 50)}"></span>
<!-- 숫자 포맷 -->
<span th:text="${#numbers.formatInteger(count, 3, 'COMMA')}"></span>
<!-- null 체크 조건부 렌더링 -->
<p th:if="${doc.description}" th:text="${doc.description}">설명</p>
새 페이지 작성 체크리스트
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>페이지 제목 - 개발 가이드</title>
<link rel="stylesheet" th:href="@{/css/guide.css}">
</head>
<body>
<!-- 페이지 콘텐츠 -->
<script th:src="@{/js/guide.js}"></script>
</body>
</html>
ISMS-P 프론트엔드 보안 체크리스트
- [ ] 사용자 입력 데이터 →
th:text 이스케이프 처리
- [ ] POST 폼 → CSRF 토큰 포함
- [ ] 입력 필드 →
maxlength 속성 지정
- [ ] JavaScript →
eval(), innerHTML 미사용
- [ ] 외부 CDN → SRI(Subresource Integrity) 해시 적용 권장