본문 바로가기

개발/BACK

WEB-INF 정적 리소스 경로 찾기 - 로컬환경과 배포환경의 차이점

728x90

로컬과 배포환경의 차이, 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.css
  • http://localhost:8080/js/app.js
  • http://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 경로를 정적 리소스로 인식한다.

💡 팁: Spring Boot에서는 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 ❌ 작동 안 함 (직접 접근 불가)
⚠️ 주의: WAR 파일로 배포해도 Spring Boot는 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/ (자동 압축 해제)
💡 팁: WAR 파일을 webapps 디렉토리에 복사하면 톰캣이 자동으로 압축을 해제하고 배포한다. 파일명이 컨텍스트 경로가 된다 (예: app.war/app).

5. Spring Boot에서의 정적 리소스

Spring Boot는 정적 리소스를 자동으로 처리한다. 설정을 통해 경로를 커스터마이징할 수 있다.

5.1 기본 정적 리소스 경로

Spring Boot는 다음 경로를 자동으로 정적 리소스로 인식한다 (우선순위 순):

  1. /META-INF/resources/
  2. /resources/
  3. /static/
  4. /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.css
  • http://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는 클라이언트가 직접 접근할 수 없는 보호된 디렉토리

 

카카오톡 오픈채팅 링크

https://open.kakao.com/o/seCteX7h

728x90