26/08/06 TIL - 레이아웃과 Thymeleaf Fragment, 서버사이드 예외처리 (1)

Let's take a break·2026년 8월 6일

Thymeleaf Fragment와 Layout을 활용한 화면 구성 및 DTO 기반 CRUD 화면 설계

이번 프로젝트에서는 기존의 Spring MVC 프로젝트를 확장하여 Thymeleaf Fragment를 활용한 화면 재사용, 공통 Layout 구성, DTO를 활용한 화면과 도메인 객체의 분리, 그리고 영화(Movie) CRUD 화면 구성을 학습하였다.

이전에는 HTML 페이지마다 <head>, <nav>, 오류 출력 영역 등을 각각 작성하였다면, 이번 프로젝트에서는 이러한 공통 요소를 Fragment로 분리하여 필요한 화면에서 재사용하는 구조를 구현하였다.

또한 사용자가 입력하는 데이터를 처리하기 위한 MovieFormDTO와 화면에 출력하기 위한 MovieViewDTO를 분리하여 사용하면서 DTO를 목적에 따라 구분하는 방법도 학습하였다.

이를 통해 화면 구성의 중복을 줄이고, 유지보수성을 높이며, Controller와 View가 역할에 맞게 동작하는 구조를 이해할 수 있었다.


Fragment(프래그먼트)의 개념

이번 프로젝트에서 가장 먼저 새롭게 학습한 내용은 Fragment이다.

Fragment란 여러 HTML 화면에서 반복적으로 사용되는 부분을 하나의 파일로 분리하여 재사용하는 기능이다.

예를 들어 모든 페이지에는 다음과 같은 요소가 반복된다.

  • <head>
  • Navigation Bar
  • Footer
  • Validation Error
  • 공통 CSS
  • 공통 JavaScript

기존 방식이라면

list.html

↓

head 작성

↓

nav 작성

↓

body 작성

──────────────

detail.html

↓

head 작성

↓

nav 작성

↓

body 작성

처럼 모든 HTML에 같은 내용을 복사해야 한다.

이 방식은 Navigation 메뉴 하나를 수정하더라도 모든 HTML을 수정해야 한다.

이번 프로젝트에서는 이러한 문제를 해결하기 위해 Fragment를 사용하였다.

fragments

↓

head.html

↓

nav.html

↓

errorMsg.html

↓

각 화면에서 참조

즉, 공통 화면을 한 곳에서 관리하고 필요한 화면에서 불러오는 구조를 학습하였다.


th:fragment를 이용한 화면 조각 정의

Fragment는 th:fragment를 이용하여 정의하였다.

예를 들어 Navigation을 하나의 Fragment로 정의하면

<nav th:fragment="mainNav">

    <a th:href="@{/movies}">
        영화 목록
    </a>

</nav>

(프로젝트 구조와 동일한 형태)

th:fragment="mainNav"

현재 HTML의 <nav> 태그를

mainNav라는 이름의 재사용 가능한 조각으로 등록한다.

Spring Boot 실행

↓

fragments/nav.html

↓

mainNav 등록

↓

다른 HTML에서 참조 가능

이렇게 등록된 Fragment는

목록 화면

상세 화면

등록 화면

수정 화면

모두 동일하게 사용할 수 있다.

즉, Navigation을 한 번만 작성하면 프로젝트 전체에서 재사용할 수 있다는 점을 학습하였다.


th:replace와 th:insert의 차이

Fragment를 사용하는 방법에는 여러 가지가 있지만 이번 프로젝트에서는 th:replaceth:insert의 차이를 함께 학습하였다.

예를 들어

<div
th:replace="~{fragments/nav :: mainNav}">
</div>

<div
th:insert="~{fragments/nav :: mainNav}">
</div>

은 결과가 서로 다르다.

th:replace

div 태그

↓

삭제

↓

Fragment로 교체

즉,

원래 <div>는 사라지고

Fragment 자체가 그 위치를 대신한다.

예를 들면

<div
th:replace="...">
</div>

렌더링 후

<nav>

...

</nav>

가 된다.


th:insert

반대로

div 태그

↓

유지

↓

안쪽에 Fragment 삽입

된다.

렌더링 후에는

<div>

<nav>

...

</nav>

</div>

구조가 된다.

속성결과
th:replace기존 태그 제거 후 Fragment로 교체
th:insert기존 태그 유지 후 내부에 Fragment 삽입

프로젝트에서는 불필요한 태그를 남기지 않기 위해 대부분 th:replace를 사용하는 것이 적합하다는 점을 이해하였다.


공통 Head Fragment 구성

모든 화면에는

  • title
  • charset
  • css
  • javascript

등이 반복된다.

이번 프로젝트에서는 이를 하나의 head.html Fragment로 분리하였다.

기존에는 head 태그 부분이 모든 html에 반복되었다.

이 head 태그 부분을 분리하여 다른 html이 분리된 Fragment를 쓰는
구조로 변경하였다.

이렇게 하면

CSS를 하나 추가할 때

모든 HTML을 수정하는 것이 아니라

head.html 하나만 수정하면 된다.

이를 통해 중복 코드를 줄이는 가장 좋은 방법은 공통 부분을 분리하는 것이라는 점을 배웠다.


프로젝트의 Navigation 역시 Fragment로 관리하였다.

Navigation에는

  • 영화 목록

  • 영화 등록

등 공통 메뉴가 존재하였다.

동작 구조는

사용자 요청

↓

HTML

↓

th:replace

↓

nav Fragment

↓

최종 HTML 생성

이다.

따라서 메뉴를 수정하더라도

Navigation Fragment 하나만 수정하면

모든 화면에 자동으로 반영된다.

이는 유지보수성과 확장성을 크게 높여 주는 구조라는 점을 이해하였다.


Validation 오류 출력 Fragment(errorMsg)

이번 프로젝트에서는 Validation 오류도 Fragment로 분리하였다.

기존에는

모든 Form마다 오류 출력 블록을 계속 작성하였다.

이번 프로젝트에서는 errorMsg Fragment를 등록 화면과 수정 화면에 재사용하는 구조를 사용하였다.

이렇게 하면 Validation을 사용하는 Form이 늘어나더라도

동일한 Fragment를 가져다 사용할 수 있다.

즉, 오류 출력 방식이 변경되더라도 Fragment 하나만 수정하면 된다는 점을 학습하였다.


Layout을 이용한 화면 구성

이번 프로젝트에서는 화면을

  • Header

  • Navigation

  • Main

  • Footer

처럼 여러 부분으로 나누어 구성하였다.

전체 구조는 다음과 같다.

Head Fragment

↓

Navigation Fragment

↓

Body

↓

Footer

각 화면은 필요한 Body만 작성하고

공통 부분은 Fragment에서 가져온다.

예를 들어

Movie List

↓

Head

↓

Nav

↓

Movie Table

────────────

Movie Detail

↓

Head

↓

Nav

↓

Movie Info

처럼 Body만 달라지고

나머지는 모두 동일하게 유지된다.

이를 통해 레이아웃과 실제 화면을 분리하는 구조를 학습하였다.


Validation과 Fragment의 연결

등록 화면에서는

Validation이 실패하면

MovieFormDTO

↓

BindingResult

↓

Error Fragment

↓

오류 출력

순서로 동작한다.

Controller는

오류를 생성하기만 하고

실제 화면 출력은 Fragment가 담당한다.

이를 통해 Controller와 View의 역할을 명확하게 분리하는 구조를 이해하였다.

0개의 댓글