로컬과 배포환경의 차이, WAR 파일 구조 완전 정복
Java 웹 애플리케이션을 개발하다 보면 WEB-INF/static 경로에 대해 헷갈리는 경우가 많다. 로컬 환경에서는 잘 작동하던 정적 리소스가 배포 환경에서 404 오류를 내는 경우가 흔하다. 이 글에서는 WEB-INF/static의 구조와 로컬/배포 환경의 차이, WAR 파일 구조를 완벽하게 설명한다.
목차
1. WEB-INF란 무엇인가?
WEB-INF는 Java 웹 애플리케이션의 표준 디렉토리 구조다. 서블릿 컨테이너가 인식하는 특별한 폴더이며, 클라이언트에서 직접 접근할 수 없는 보호된 영역이다.
1.1 WEB-INF의 특징
- 보호된 디렉토리: 클라이언트가 직접 URL로 접근할 수 없다
- 서블릿 컨테이너 전용: 서블릿 컨테이너만 접근 가능
- 표준 구조: Java EE/Jakarta EE 표준에 정의된 구조
1.2 WEB-INF 디렉토리 구조
webapp/
├── WEB-INF/
│ ├── web.xml # 웹 애플리케이션 배포 설명자
│ ├── classes/ # 컴파일된 클래스 파일
│ ├── lib/ # JAR 라이브러리
│ ├── static/ # 정적 리소스 (Spring Boot)
│ │ ├── css/
│ │ ├── js/
│ │ └── images/
│ └── views/ # JSP, Thymeleaf 템플릿 등
├── index.html # 루트에 있는 정적 파일 (직접 접근 가능)
└── assets/ # 루트에 있는 정적 파일 (직접 접근 가능)
WEB-INF 안의 파일은 클라이언트가 직접 URL로 접근할 수 없다. 서블릿이나 컨트롤러를 통해서만 접근 가능하다.2. 정적 리소스 경로 구조
정적 리소스(HTML, CSS, JavaScript, 이미지 등)를 배치하는 위치에 따라 접근 방법이 달라진다.
2.1 루트 디렉토리에 배치 (직접 접근 가능)
웹 애플리케이션 루트에 배치하면 클라이언트가 직접 URL로 접근할 수 있다.
webapp/
├── css/
│ └── style.css
├── js/
│ └── app.js
└── images/
└── logo.png
접근 URL:
http://localhost:8080/css/style.csshttp://localhost:8080/js/app.jshttp://localhost:8080/images/logo.png
2.2 WEB-INF/static에 배치 (Spring Boot)
Spring Boot는 WEB-INF/static 또는 src/main/resources/static에 정적 리소스를 배치한다.
src/main/
├── resources/
│ ├── static/ # Spring Boot 정적 리소스
│ │ ├── css/
│ │ ├── js/
│ │ └── images/
│ └── templates/ # 템플릿 파일 (Thymeleaf, JSP 등)
└── webapp/
└── WEB-INF/
└── static/ # 또는 여기에 배치
Spring Boot는 자동으로 /static, /public, /resources, /META-INF/resources 경로를 정적 리소스로 인식한다.
src/main/resources/static을 사용하는 것이 권장된다. 빌드 시 자동으로 클래스패스에 포함된다.3. 로컬 환경 vs 배포 환경
로컬 환경과 배포 환경에서 정적 리소스 접근 방식이 다르다. 이 차이를 이해하지 못하면 배포 후 404 오류가 발생한다.
3.1 로컬 환경 (개발 환경)
로컬에서 Spring Boot를 실행하면 내장 톰캣이 정적 리소스를 자동으로 서빙한다.
로컬 환경 구조:
프로젝트 루트/
├── src/main/
│ └── resources/
│ └── static/
│ ├── css/style.css
│ └── js/app.js
└── target/classes/ # 빌드 결과물
└── static/
├── css/style.css
└── js/app.js
접근 URL (로컬):
http://localhost:8080/css/style.css✅ 작동http://localhost:8080/js/app.js✅ 작동
3.2 배포 환경 (WAR 파일)
WAR 파일로 배포하면 구조가 완전히 달라진다. WAR 파일을 압축 해제하면 다음과 같은 구조가 나온다.
WAR 파일 내부 구조:
app.war (압축 해제 시)
├── WEB-INF/
│ ├── classes/
│ │ └── static/ # src/main/resources/static이 여기로 복사됨
│ │ ├── css/style.css
│ │ └── js/app.js
│ ├── lib/ # 의존성 JAR 파일들
│ └── web.xml
├── META-INF/
└── index.html # 루트에 있는 파일
배포 환경에서의 접근:
http://example.com/css/style.css✅ 작동 (Spring Boot가 자동 처리)http://example.com/WEB-INF/classes/static/css/style.css❌ 작동 안 함 (직접 접근 불가)
WEB-INF/classes/static의 리소스를 자동으로 서빙한다. 하지만 WEB-INF/static 경로를 직접 사용하면 안 된다.3.3 주요 차이점 정리
| 항목 | 로컬 환경 | 배포 환경 (WAR) |
|---|---|---|
| 정적 리소스 위치 | src/main/resources/static |
WEB-INF/classes/static |
| 접근 URL | /css/style.css |
/css/style.css |
| 서빙 방식 | Spring Boot 내장 톰캣 | 외부 톰캣 + Spring Boot |
| 빌드 결과물 | target/classes/static |
WAR 파일 내부 |
4. WAR 파일 구조
WAR(Web Application Archive) 파일은 Java 웹 애플리케이션을 배포하기 위한 표준 포맷이다. JAR 파일과 유사하지만 웹 애플리케이션에 특화된 구조를 가진다.
4.1 WAR 파일 생성
Maven이나 Gradle로 WAR 파일을 생성한다.
Maven (pom.xml):
<packaging>war</packaging>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
빌드 명령어:
mvn clean package
# 결과: target/app.war
4.2 WAR 파일 내부 구조 상세
app.war
│
├── META-INF/
│ ├── MANIFEST.MF # 메타 정보
│ └── maven/ # Maven 메타 정보
│
├── WEB-INF/
│ ├── web.xml # 웹 애플리케이션 배포 설명자
│ ├── classes/ # 컴파일된 클래스 파일
│ │ ├── com/
│ │ │ └── example/
│ │ │ └── Application.class
│ │ └── static/ # src/main/resources/static이 여기로 복사
│ │ ├── css/
│ │ ├── js/
│ │ └── images/
│ ├── lib/ # 의존성 JAR 파일들
│ │ ├── spring-boot-2.x.x.jar
│ │ ├── spring-web-5.x.x.jar
│ │ └── ...
│ └── views/ # 템플릿 파일 (선택적)
│
└── index.html # 루트에 있는 정적 파일 (직접 접근 가능)
css/ # 루트에 있는 정적 파일 (직접 접근 가능)
js/
4.3 WAR 파일 배포
WAR 파일을 톰캣에 배포하는 방법:
# 방법 1: webapps 디렉토리에 복사
cp app.war /path/to/tomcat/webapps/
# 방법 2: Manager 앱 사용
curl -u admin:password \
"http://localhost:8080/manager/text/deploy?path=/app&war=file:/path/to/app.war"
# 방법 3: 자동 배포 (톰캣이 자동으로 압축 해제)
# webapps/app.war → webapps/app/ (자동 압축 해제)
webapps 디렉토리에 복사하면 톰캣이 자동으로 압축을 해제하고 배포한다. 파일명이 컨텍스트 경로가 된다 (예: app.war → /app).5. Spring Boot에서의 정적 리소스
Spring Boot는 정적 리소스를 자동으로 처리한다. 설정을 통해 경로를 커스터마이징할 수 있다.
5.1 기본 정적 리소스 경로
Spring Boot는 다음 경로를 자동으로 정적 리소스로 인식한다 (우선순위 순):
/META-INF/resources//resources//static//public/
5.2 정적 리소스 경로 커스터마이징
application.properties 또는 application.yml에서 설정할 수 있다.
# application.properties
spring.web.resources.static-locations=classpath:/static/,classpath:/public/,file:./uploads/
spring.web.resources.cache.period=3600
spring.web.resources.chain.strategy.content.enabled=true
# application.yml
spring:
web:
resources:
static-locations:
- classpath:/static/
- classpath:/public/
- file:./uploads/
cache:
period: 3600
chain:
strategy:
content:
enabled: true
5.3 WebMvcConfigurer로 커스터마이징
더 세밀한 제어가 필요하면 WebMvcConfigurer를 사용한다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 정적 리소스 핸들러 추가
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(3600);
// 외부 디렉토리도 추가 가능
registry.addResourceHandler("/uploads/**")
.addResourceLocations("file:./uploads/");
}
}
6. 실제 예제와 해결 방법
실제 개발 중 발생하는 문제와 해결 방법을 예제로 설명한다.
6.1 문제: 배포 후 CSS/JS 파일이 404 오류
로컬에서는 잘 작동하던 정적 리소스가 배포 후 404 오류를 낸다.
원인:
- 정적 리소스가
src/main/webapp에만 있고src/main/resources/static에 없음 - 빌드 설정에서 정적 리소스가 제외됨
- WAR 파일에 정적 리소스가 포함되지 않음
해결 방법:
<!-- pom.xml -->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
<!-- webapp의 정적 리소스도 포함 -->
<resource>
<directory>src/main/webapp</directory>
<targetPath>static</targetPath>
<includes>
<include>**/*.css</include>
<include>**/*.js</include>
<include>**/*.png</include>
</includes>
</resource>
</resources>
</build>
6.2 문제: Thymeleaf 템플릿에서 정적 리소스 참조
Thymeleaf 템플릿에서 정적 리소스를 참조할 때 경로가 잘못된다.
잘못된 방법:
<!-- ❌ 잘못된 경로 -->
<link rel="stylesheet" href="/WEB-INF/static/css/style.css">
<script src="/WEB-INF/classes/static/js/app.js"></script>
올바른 방법:
<!-- ✅ 올바른 경로 -->
<link rel="stylesheet" th:href="@{/css/style.css}">
<script th:src="@{/js/app.js}"></script>
<!-- 또는 절대 경로 -->
<link rel="stylesheet" href="/css/style.css">
<script src="/js/app.js"></script>
6.3 문제: 컨텍스트 경로가 있는 경우
애플리케이션이 /app 같은 컨텍스트 경로로 배포되는 경우다.
설정 방법:
# application.properties
server.servlet.context-path=/app
# application.yml
server:
servlet:
context-path: /app
접근 URL:
http://example.com/app/css/style.csshttp://example.com/app/js/app.js
Thymeleaf에서 사용:
<!-- Thymeleaf가 자동으로 컨텍스트 경로를 추가 -->
<link rel="stylesheet" th:href="@{/css/style.css}">
<!-- 결과: /app/css/style.css -->
6.4 완전한 예제 프로젝트 구조
myapp/
├── src/
│ └── main/
│ ├── java/
│ │ └── com/example/
│ │ └── Application.java
│ ├── resources/
│ │ ├── static/ # ✅ 권장 위치
│ │ │ ├── css/
│ │ │ │ └── style.css
│ │ │ ├── js/
│ │ │ │ └── app.js
│ │ │ └── images/
│ │ │ └── logo.png
│ │ ├── templates/
│ │ │ └── index.html
│ │ └── application.yml
│ └── webapp/ # 선택적 (기존 프로젝트 호환)
│ ├── WEB-INF/
│ │ └── web.xml
│ └── index.html
├── pom.xml
└── target/
└── app.war # 빌드 결과물
마무리
WEB-INF/static은 Spring Boot에서 정적 리소스를 배치하는 표준 위치다. 로컬 환경과 배포 환경의 차이를 이해하고, WAR 파일 구조를 파악하면 배포 후 발생하는 문제를 쉽게 해결할 수 있다.
가장 중요한 것은 src/main/resources/static에 정적 리소스를 배치하는 것이다. 이렇게 하면 빌드 시 자동으로 WEB-INF/classes/static으로 복사되고, Spring Boot가 자동으로 서빙한다.
- 정적 리소스는
src/main/resources/static에 배치 - 로컬과 배포 환경 모두 동일한 URL로 접근 가능 (
/css/style.css) - WAR 파일 내부에서는
WEB-INF/classes/static에 위치 - Thymeleaf에서는
@{/css/style.css}형식 사용 - 컨텍스트 경로가 있으면 자동으로 추가됨
WEB-INF는 클라이언트가 직접 접근할 수 없는 보호된 디렉토리

카카오톡 오픈채팅 링크
'개발 > BACK' 카테고리의 다른 글
| SpringBoot QueryDSL 환경에서 Servlet 구현 예제 (0) | 2025.12.24 |
|---|---|
| SpringBoot 파일 전송 구현하기 (MVC 패턴 + QueryDSL) (0) | 2025.12.24 |
| Linux vi / vim 명령어 총 정리 (0) | 2025.12.24 |
| Angular + Ionic + Capacitor에서 Firebase 완벽 연동 가이드 예제포 (0) | 2025.12.21 |
| Spring Boot 환경에서 "Java Error Occurred During Initialization of Boot Layer" 에러 해결 방법 (0) | 2024.07.12 |