본문 바로가기
Front-end

[Thymeleaf] 타임리프 레이아웃 적용하기(Spring Boot) - 레이아웃 의존성 필요X

by 셀킴 2024. 11. 14.
728x90
반응형

우선 레이아웃이 적용된 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.htmlheader 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.htmlfragment-name이라는 fragment를 가져와 삽입하라는 문장입니다

 

filename.html에 딱 한 줄, section태그로 fragment-name을 정의 했습니다

 

section 태그에 핑크색 배경을 적용했는데 코드가 잘 로드되었고 css도 적용이 되고 있죠? 

footer 부분은 header와 다를 게 없기 때문에 생략합니다

 

서브페이지도 같은 방식으로 구현했습니다

타이틀이 유동적인 점이 👍

 

 

여기까지 Thymeleaf 문법과 layout.html 파일로 레이아웃을 재사용하는 방법을 알아보았습니다!

 

728x90
반응형