우선 레이아웃이 적용된 index.html 결과물
스프링부트에서 타임리프 템플릿 레이아웃을 구현한 코드를 깃헙 레포지토리로 올렸습니다
제가 타임리프 레이아웃이 헷갈려서 검색할 때 보니 참고할만한 레퍼런스를 찾기 어렵더라고요
언젠가 누군가에겐 도움이 되겠거니 기대합니다
이 방법은 layout dialect 의존성이 필요하지 않습니다
기본 타임리프 의존성만 있으면 됨
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
그리고 application.properties에 thymeleaf prefix를 따로 설정하지 않았습니다
원래 명시 안해도 자동으로 적용이 된다고 합니다
타임리프 템플릿엔진은 기본적으로 src/main/resources 하위의 templates 경로를 찾아갑니다
이미지, css 등 정적 리소스는 static 폴더를 찾아가고요
따라서 폴더 구조는 이런 식으로 구성했습니다
먼저 레이아웃을 정의한 layout.html 코드입니다
차근차근 살펴보겠습니다
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:fragment="layout(title, content)">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title th:replace="${title}"></title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<header th:replace="~{common/header :: header}"></header>
<main th:replace="${content}"></main>
<footer th:replace="~{common/footer :: footer}"></footer>
</body>
</html>
html 태그에 타임리프 url이 필요하고요, fragment 선언이 들어갔습니다
th:fragment="layout(title,content)"
여기서 layout은 이 HTML 파일을 레이아웃 템플릿으로 선언하는 이름입니다
title,content는 이 레이아웃을 사용하는 페이지로부터 전달받을 매개변수로, 각각 head 태그의 title과 페이지마다 바뀌는 content 영역에 해당합니다
layout.html을 사용하는 모든 페이지에 공통으로 들어가는 css 선언도 들어갔습니다
타임리프 문법으로 css 경로를 지정했습니다
@{} 구문은 src/main/resources/static 디렉토리의 파일을 찾습니다
그다음은 header, content, footer 영역입니다
<header>, <main>, <footer> 태그 이름은 중요하지 않습니다
어차피 th:replace 문법은 가져온 코드를 현재 태그와 교체합니다
영역을 명시적으로 나타내기 위해 사용한 태그일뿐 어떤 태그를 써도 결과는 같습니다
~{}는 상대 경로로 fragment를 불러오는 타임리프 문법입니다
header.htm를 보겠습니다
<!--<div th:fragment="header" class="header-wrapper"> -->
<header>
<h1>헤더입니다</h1>
<ul>
<li><a href="/">홈</a></li>
<li><a href="/sub">서브</a></li>
</ul>
</header>
<!--</div>-->
만약 제가 <header th:insert="~{common/header :: header}"></header> 이렇게 정의하면,
렌더링 될 때 이렇게 header 태그가 중복이 되어버립니다
이런 불필요한 중첩을 피하기 위해 th:replace를 사용했습니다
th:replace, th:insert, th:include의 차이는 공식 문서를 참조 바랍니다
다시 header.html 코드로 돌아와서!
제가 왜 header 태그 위에 <!--<div th:fragment="header" class="header-wrapper"> -->라인을 주석으로 추가해 두었을까요?
fragment 개념에 대해 설명하기 위해서입니다
layout.html의
th:replace="~{common/header :: header}
위 내용은 common/header.html의 header fragment를 찾아서 replace 하라는 뜻입니다
여기서
1. 타임리프는 먼저 th:fragment="header"가 명시적으로 정의된 요소를 찾습니다
2. fragment 정의가 없을 경우에는 header 태그를 가져오고
그 마저도 없을 경우에는 가져오지 못하고 그 결과 An error happened during template parsing (template: "class path resource [templates/index.html]") 500에러가 납니다
2번의 경우에 루트 태그가 header가 아니라면 전체 코드에서 header 태그 부분만 가져옵니다
예를 들어 루트가 div 태그이고 그 하위에 header 태그가 있는 경우 아래 이미지처럼 header 영역 밖의 코드는 불러오지 못합니다
그다음은 index.html 코드입니다
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{common/layout :: layout(~{::title}, ~{::content})}">
<head>
<title>Thymeleaf layout 예제 - 인덱스</title>
</head>
<body>
<th:block th:fragment="content">
<div class="container">
<h2>인덱스페이지 영역입니다</h2>
<th:block th:replace="~{common/filename :: fragment-name}"></th:block>
</div>
</th:block>
</body>
</html>
<html ..생략... th:replace="~{common/layout :: layout(~{::title}, ~{::content})}">
현재 <html> 태그를 common/layout.html의 layout fragment로 대체하며 매개변수로 ~{::title}과 ~{::content}를 전달합니다
~{::title}: 현재 템플릿에서 title 태그의 내용을 의미합니다
~{::content}: 현재 템플릿에서 content fragment를 의미합니다(fragment 정의 필수)
레이아웃은 이 매개변수를 받아 동적으로 HTML을 구성합니다
앞서 layout에서는 html에 th:fragment="layout(title,content)"라고 정의를 했었죠?
title과 content 외의 부분은 layout.html의 코드로 대체됩니다
즉, layout.html에서 정의된 구조(헤더, 푸터, 공통 스타일 등)는 유지되고, title과 content 부분만 동적으로 대체됩니다
다시 index.html코드를 보시면
<th:block th:insert="~{common/filename :: fragment-name}"></th:block>
라인이 있습니다
common/filename.html의 fragment-name이라는 fragment를 가져와 삽입하라는 문장입니다
filename.html에 딱 한 줄, section태그로 fragment-name을 정의 했습니다
section 태그에 핑크색 배경을 적용했는데 코드가 잘 로드되었고 css도 적용이 되고 있죠?
footer 부분은 header와 다를 게 없기 때문에 생략합니다
서브페이지도 같은 방식으로 구현했습니다
여기까지 Thymeleaf 문법과 layout.html 파일로 레이아웃을 재사용하는 방법을 알아보았습니다!
'Front-end' 카테고리의 다른 글
[HTML/SCSS] Swiper 썸네일 갤러리 with 텍스트 슬라이더 (1) | 2024.02.29 |
---|---|
[HTML/SCSS] Swiper 하나에 여러 pagination(fraction+progressbar) 커스텀 적용하는 방법 (0) | 2024.02.23 |
[HTML/SCSS] Swiper로 만드는 자연스러운 슬라이드 이동 효과 (0) | 2024.01.29 |
[HTML] HTML Entity 코드로 특수문자 입력하기 (1) | 2023.12.07 |
[CSS] VSCODE emmet(에밋) 기능 더 빠르게 사용하기 (1) | 2023.11.30 |