새로운 Spring Boot 프로젝트를 실행하고 localhost:8080에 접속했을 때, 브라우저 탭에 나타나는 기본 '초록색 나뭇잎' 아이콘을 본 적이 있다면 제가 무슨 말을 하는지 정확히 아실 겁니다. 그 작은 나뭇잎은 Java 개발자들에게는 훈장과도 같지만, 사용자들은 여러분의 실제 브랜드 로고를 보길 원합니다.

Stripe나 GitHub 같은 기업들이 웹 앱을 어떻게 관리하는지 살펴보세요. 그들은 프로덕션 빌드에 프레임워크의 기본 아이콘을 남겨두지 않습니다. 페이지 렌더링이 끝나기도 전에 선명하고 최적화된 아이콘을 제공하여 즉각적인 신뢰를 구축합니다. 아이콘이 없거나 기본 아이콘을 그대로 두면 "아직 완성되지 않은 사이드 프로젝트"라는 인상을 주게 됩니다.

오래된 튜토리얼들은 대부분 ICO 파일을 static 폴더에 넣기만 하면 끝이라고 말합니다. 하지만 저는 거기서 멈추는 것을 강력히 반대합니다. 우리는 2026년에 웹 앱을 구축하고 있습니다. 즉, 고해상도 디스플레이와 다크 모드를 처리하면서 Spring의 엄격한 라우팅 규칙과도 잘 연동되는 모던 스택이 필요합니다.

빠른 해결책: Static 폴더 방식

Spring Boot는 정적 리소스 처리를 위한 내장된 마법을 가지고 있습니다. 기본적으로 특정 클래스패스 디렉토리에서 favicon.ico라는 이름의 파일을 찾아 루트 URL(/favicon.ico)에 자동으로 매핑합니다.

1단계: 아이콘 파일 준비하기

먼저 유효한 ICO 파일이 필요합니다. 단순히 PNG 파일의 확장자만 ICO로 바꾸지 마세요(브라우저가 매우 싫어합니다). Mzu favicondl을 사용하여 로고에서 올바른 다중 해상도 ICO 파일을 생성하세요.

2단계: 올바른 디렉토리에 배치하기

새로 생성한 favicon.ico를 Spring Boot 프로젝트의 다음 디렉토리 중 하나에 넣습니다:

애플리케이션을 재시작합니다. 루트 URL에 접속하면 초록색 나뭇잎이 사라지고 커스텀 아이콘으로 대체된 것을 확인할 수 있습니다.

모던한 접근법: Thymeleaf와 HTML 태그

암묵적인 루트 favicon.ico 요청에만 의존하는 것은 과거의 습관입니다. 브라우저가 요청하긴 하겠지만, Apple Touch Icon이나 최신 SVG 포맷을 제어할 수는 없습니다.

HTML 템플릿에 아이콘을 명시적으로 선언해야 합니다. Thymeleaf(Spring의 표준 템플릿 엔진)를 사용 중이라면 재사용 가능한 <head> 프래그먼트를 만들 수 있습니다.

<!-- fragments/head.html 내부 -->
<link rel='icon' type='image/svg+xml' href='/icons/favicon.svg'>
<link rel='icon' type='image/png' href='/icons/favicon-96x96.png' sizes='96x96'>
<link rel='apple-touch-icon' href='/icons/apple-touch-icon.png'>

이 파일들을 src/main/resources/static/icons/ 안에 배치하세요. 이렇게 하면 브라우저에 정확히 무엇을 로드해야 할지 명시하여 폴백 루트 요청을 완전히 우회할 수 있습니다. 정확히 어떤 태그를 사용해야 할지 다시 확인하고 싶다면 Favicon 추가를 위한 HTML 가이드를 참조하세요.

주의사항: Spring Security가 아이콘을 차단할 때

이 부분이 90%의 Java 개발자가 막히는 곳입니다. 파일을 올바른 폴더에 넣고 HTML 태그도 추가했는데 브라우저 탭이 완전히 비어있습니다. DevTools 네트워크 탭을 열어보면 404 Not Found 또는 로그인 페이지로의 302 Redirect가 표시됩니다.

클래스패스에 Spring Security가 있다면 정적 리소스를 포함한 모든 엔드포인트를 기본적으로 보호합니다. 브라우저가 /favicon.ico를 가져오려 할 때 Spring Security가 인증되지 않은 요청을 가로채고 차단하는 것입니다.

Security Filter Chain 수정 방법

Spring Security에 favicon 및 정적 아이콘 디렉토리에 대한 요청을 무시하도록 명시적으로 알려야 합니다. 보안 설정 클래스를 열고 SecurityFilterChain 빈을 업데이트하세요:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers('/favicon.ico', '/icons/**').permitAll()
            .anyRequest().authenticated()
        )
        .formLogin(Customizer.withDefaults());
    return http.build();
}

requestMatchers('/favicon.ico', '/icons/**').permitAll()을 추가함으로써 사용자가 로그인하기 전에 브라우저가 브랜드 아이콘을 가져올 수 있도록 허용합니다. 이는 로그인 페이지 자체를 프로페셔널하게 보이게 하는 데 매우 중요합니다.

자주 발생하는 문제들

완벽한 Spring 설정을 마쳤더라도 여전히 문제가 발생할 수 있습니다. 주로 다음과 같은 원인들입니다:

Spring Boot에서 favicon을 커스터마이징하는 것은 라우팅과 보안 계층 때문에 정적 HTML 사이트보다 조금 더 노력이 필요합니다. 하지만 그 기본 나뭇잎을 제거하는 것이 로컬 Java 프로젝트를 프로덕션 수준의 웹 애플리케이션으로 전환하는 첫걸음입니다.