하이어코딩 RSS 태그 관리 글쓰기 방명록 mahiru
전체 글 (45)
2025-07-23 00:55:55
728x90
반응형

들어가며

터미널에서 직접 AI에게 코딩 작업을 위임할 수 있다면 어떨까요? Anthropic의 Claude Code는 이러한 상상을 현실로 만든 혁신적인 도구입니다. 오늘은 Claude Code를 설치하고 활용하는 방법을 상세히 알아보겠습니다.

Claude Code란?

Claude Code는 개발자들이 터미널에서 직접 Claude에게 코딩 작업을 위임할 수 있는 Agentic Command Line Tool입니다. 현재 Research Preview 단계로, 얼리 어답터들의 피드백을 받으며 지속적으로 개선되고 있습니다.

주요 특징

  • 🖥️ 터미널 기반: IDE를 떠나지 않고 바로 사용 가능
  • 🤖 자율적 작업 수행: 단순 코드 생성을 넘어 파일 생성, 수정, 실행까지
  • 🔄 컨텍스트 인식: 프로젝트 구조와 기존 코드를 이해하고 작업
  • 💬 대화형 인터페이스: 자연어로 요청하고 실시간 피드백

설치 및 설정

1. 사전 요구사항

 
 
bash
# Node.js 18.0 이상 필요
node --version

# npm 또는 yarn 설치 확인
npm --version

2. Claude Code 설치

 
 
bash
# npm을 사용하는 경우
npm install -g claude-code

# yarn을 사용하는 경우
yarn global add claude-code

3. API 키 설정

Claude Code를 사용하려면 Anthropic API 키가 필요합니다.

 
 
bash
# 환경 변수 설정 (Linux/Mac)
export ANTHROPIC_API_KEY="your-api-key-here"

# Windows PowerShell
$env:ANTHROPIC_API_KEY="your-api-key-here"

영구적으로 설정하려면 .bashrc, .zshrc 또는 시스템 환경 변수에 추가하세요.

4. 초기 설정

 
 
bash
# Claude Code 초기화
claude-code init

# 설정 확인
claude-code config

기본 사용법

1. 프로젝트에서 Claude Code 시작

 
 
bash
# 프로젝트 디렉토리로 이동
cd my-project

# Claude Code 시작
claude-code

2. 기본 명령어

 
 
bash
# 새 파일 생성
> Create a new React component called UserProfile

# 기존 파일 수정
> Add error handling to the fetchData function in api.js

# 리팩토링
> Refactor the authentication logic to use hooks

# 테스트 작성
> Write unit tests for the Calculator class

3. 고급 활용

 
 
bash
# 복잡한 작업 요청
> Create a REST API with Express that handles user authentication, 
  includes JWT tokens, and connects to MongoDB

# 디버깅 도움
> Help me debug why this async function is not returning the expected value

# 코드 리뷰
> Review the security of my login implementation and suggest improvements

실전 예제: Todo 앱 만들기

실제로 Claude Code를 사용해 간단한 Todo 앱을 만들어보겠습니다.

Step 1: 프로젝트 초기화

 
 
bash
mkdir todo-app && cd todo-app
claude-code

Step 2: 기본 구조 생성

 
 
> Create a Node.js Express server with the following:
  - Basic server setup on port 3000
  - Routes for CRUD operations on todos
  - In-memory data storage for now
  - Proper error handling

Step 3: 프론트엔드 추가

 
 
> Create a simple HTML/CSS/JavaScript frontend that:
  - Displays all todos
  - Allows adding new todos
  - Allows marking todos as complete
  - Allows deleting todos
  - Uses fetch API to communicate with the backend

Step 4: 개선사항 적용

 
 
> Add the following improvements:
  - Input validation on the backend
  - Loading states on the frontend
  - Responsive design
  - Local storage fallback

효과적인 사용을 위한 팁

1. 명확한 요청 작성

 
 
bash
# ❌ 모호한 요청
> Make it better

# ✅ 구체적인 요청
> Optimize the database queries in userController.js by adding 
  proper indexing and implementing pagination

2. 단계적 접근

큰 작업은 작은 단위로 나누어 요청하세요:

 
 
bash
# 1단계: 구조 설계
> Design the folder structure for a scalable React application

# 2단계: 핵심 컴포넌트
> Implement the core routing setup with React Router

# 3단계: 상태 관리
> Add Redux Toolkit for state management with user slice

3. 컨텍스트 제공

 
 
bash
# 프로젝트 정보 제공
> This is a e-commerce project using Next.js 14 with TypeScript.
  Create a product listing page with server-side rendering

장점과 한계

장점

  • 빠른 프로토타이핑: 아이디어를 빠르게 구현
  • 학습 도구: 코드 예제와 설명을 실시간으로 확인
  • 반복 작업 자동화: 보일러플레이트 코드 생성
  • 코드 품질 향상: 베스트 프랙티스 자동 적용

현재 한계

  • ⚠️ Research Preview: 아직 실험 단계로 변경 가능성 있음
  • ⚠️ 인터넷 연결 필요: 오프라인에서 사용 불가
  • ⚠️ 대규모 리팩토링: 복잡한 대규모 변경은 수동 작업 필요
  • ⚠️ 특정 프레임워크: 최신 프레임워크는 지원이 제한적일 수 있음

보안 고려사항

1. API 키 관리

 
 
bash
# .env 파일 사용
ANTHROPIC_API_KEY=sk-ant-...

# .gitignore에 추가
echo ".env" >> .gitignore

2. 민감한 정보

  • 프로덕션 데이터베이스 정보 노출 주의
  • 실제 API 키나 비밀번호를 코드에 포함시키지 않기
  • 생성된 코드 검토 후 사용

문제 해결

일반적인 문제들

  1. API 키 인식 오류
 
 
bash
# 환경 변수 확인
echo $ANTHROPIC_API_KEY

# 다시 설정
export ANTHROPIC_API_KEY="your-key"
  1. 권한 오류
 
 
bash
# 전역 설치 권한 문제 해결
sudo npm install -g claude-code
  1. 버전 충돌
 
 
bash
# 캐시 정리 후 재설치
npm cache clean --force
npm install -g claude-code@latest

마무리

Claude Code는 개발자의 생산성을 획기적으로 향상시킬 수 있는 강력한 도구입니다. 아직 Research Preview 단계이지만, 이미 많은 개발자들이 일상적인 코딩 작업에 활용하고 있습니다.

시작은 간단합니다. 터미널을 열고, Claude Code를 설치하고, 자연어로 코딩 작업을 요청해보세요. AI와 함께하는 새로운 개발 경험이 여러분을 기다리고 있습니다.

추가 리소스

728x90
반응형
2025-07-23 00:12:40
728x90
반응형

정부지원사업에 도전하는 예비창업자라면 누구나 고민하는 것이 바로 '어떻게 하면 평가위원들의 마음을 사로잡는 사업계획서를 작성할 수 있을까?'입니다. 오늘은 예비창업패키지 선정을 위한 사업계획서 작성의 핵심 전략을 상세히 알아보겠습니다.

1. 사회적 문제에서 시작하라

개인의 불편함을 넘어 사회의 문제로

성공적인 사업계획서의 첫 번째 특징은 사회적 문제에 대한 깊은 공감입니다. 평가위원들이 가장 먼저 보는 것은 창업자가 인식한 문제의 크기와 중요성입니다.

  • ❌ "내가 불편해서 만들었다"
  • ✅ "우리 사회가 함께 해결해야 할 문제다"

예를 들어, 노인 교육 부족 문제로 인한 자살률 증가, 1인 가구 증가로 인한 펫 케어 서비스 필요성 등 구체적인 통계와 함께 제시하면 설득력이 높아집니다.

문제 제시의 기술

  1. 구체적인 통계 데이터 활용: 단순히 "많다", "부족하다"가 아닌 정확한 숫자로 표현
  2. 시각적 자료 활용: 사진, 그래프 등으로 문제의 심각성을 직관적으로 전달
  3. ESG 관점 포함: 환경, 사회, 거버넌스 측면에서의 문제 해결 방안 제시

2. 실행 능력을 증명하라

아이디어를 현실로 만드는 능력

좋은 아이디어는 많지만, 그것을 실제로 구현할 수 있는 팀은 드뭅니다. 평가위원들은 여러분의 실행 능력을 보고 싶어 합니다.

실행 능력을 보여주는 방법:

  • 이미 진행한 시장 검증 결과 제시
  • MVP(최소기능제품) 개발 현황
  • 고객 인터뷰나 설문조사 결과
  • 관련 분야 경험과 전문성

야놀자의 교훈

유니콘 기업 야놀자도 처음부터 완벽한 서비스를 만든 것이 아닙니다. 고객의 수요를 파악한 후 서비스를 개발했다는 점을 기억하세요. 시장의 요구를 먼저 검증하고, 그에 맞춰 서비스를 개발하는 접근이 필요합니다.

3. 경쟁력 있는 비즈니스 모델을 설계하라

사업과 비즈니스의 차이

  • 사업: 단순히 제품이나 서비스를 판매하는 것
  • 비즈니스: 지속가능한 수익 창출 시스템을 구축하는 것

성공적인 비즈니스 모델의 3가지 조건:

  1. 명확한 가치 제안: 고객이 왜 우리 제품/서비스를 선택해야 하는가?
  2. 수익 모델의 구체성: 어떻게 돈을 벌 것인가?
  3. 모방 불가능성: 경쟁자가 쉽게 따라할 수 없는 차별점은 무엇인가?

선순환 구조 만들기

가치 창출 → 고객 만족 → 수익 증가 → 재투자 → 더 큰 가치 창출

이러한 선순환 구조를 명확히 제시하면 사업의 지속가능성을 효과적으로 전달할 수 있습니다.

4. 창업자와 팀의 역량을 강조하라

왜 당신이어야 하는가?

아무리 좋은 아이템도 결국 사람이 실행합니다. 평가위원들은 "왜 이 팀이 이 문제를 가장 잘 해결할 수 있는가?"를 궁금해합니다.

팀 역량 어필 포인트:

  • 관련 분야 경험과 전문성
  • 문제에 대한 깊은 이해와 열정
  • 팀원 간의 시너지와 역할 분담
  • 외부 전문가 네트워크

전문가와의 협력

특히 기술 기반 창업의 경우, 관련 분야 전문가와의 협력 관계를 구축하는 것이 중요합니다. 초기 단계부터 전문가의 조언을 받고, 이를 사업계획서에 반영하세요.

5. 2025년 예비창업패키지 특별 전략

숫자로 말하라

2025년 트렌드와 시장 데이터를 적극 활용하세요:

  • 1인 가구 증가율
  • 고령화 속도
  • 디지털 전환 지표
  • ESG 관련 정책 변화

지역과의 연계성

창업 지원 특별구역이나 대학과의 연계 가능성을 검토하세요. 지역 특화 산업과의 시너지를 만들 수 있다면 가점 요소가 됩니다.

3년 성장 전략

최소 3년간의 구체적인 성장 로드맵을 제시하세요:

  • 1년차: MVP 개발 및 시장 검증
  • 2년차: 본격적인 시장 진입 및 확대
  • 3년차: 수익화 및 다음 단계 준비

마무리: 평가자의 입장에서 검토하라

사업계획서를 완성했다면, 이제 평가자의 입장에서 다시 한 번 검토해보세요.

체크리스트:

  • 첫 페이지를 읽고 어떤 문제를 해결하는지 명확한가?
  • 왜 이 팀이 해결할 수 있는지 설득력이 있는가?
  • 비즈니스 모델이 현실적이고 지속가능한가?
  • 정부 지원금이 왜 필요한지 타당한가?
  • 3년 후 어떤 모습일지 그려지는가?

예비창업패키지는 단순한 지원금이 아닌, 여러분의 꿈을 현실로 만드는 디딤돌입니다. 철저한 준비와 진정성 있는 사업계획서로 2025년 예비창업패키지에 도전해보세요!


💡 Tip: 사업계획서 작성 후에는 반드시 관련 분야 전문가나 선배 창업자의 피드백을 받아보세요. 객관적인 시각에서의 조언이 합격 가능성을 크게 높여줍니다.

728x90
반응형
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
반응형


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