정적 리소스 배포 시 브라우저의 캐시로 인해 변경된 내용이 반영되지 않는 이슈를 해결하기 위해
정적 리소스의 Versioning 방법을 정리하였습니다.
🤔 기존의 문제점
js, css 등 정적 리소스에 오늘 일자를 쿼리 스트링으로 추가하여 매일 새로운 파일을 다운로드 받도록 하고 있었습니다.
<script src="/r/v2/js/assets/common/common.js?ver=${useDate}"></script>
이 방식의 문제점은 일별로 쿼리 스트링이 변경 되므로 정적 리소스가 변경되지 않아도 매일 새로운 파일을 다운로드 받게 되며,
새로운 정적 리소스를 배포할 경우 이미 동일 일자에 접속한 이력이 있으면 변경된 정적 리소스를 다운로드 받지 못 하게 된다는 점입니다.
🔎 해결 방법
1) 파일명에 해시 값 추가
정적 리소스 파일명의 파일의 해시 값을 추가하여, 파일이 변경 되었을 때 새로운 해시 값이 부여되어 새로 다운로드 받을 수 있습니다.
예) <link href="/css/spring.css"/> ➡️ <link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
2) 버전 고정 값 사용
파일명의 교체 대신에 고정된 버전 값을 설정하고 이 값으로 가상의 디렉토리를 생성하여 위치하도록 resolve 하고
버전 값을 변경하면 디렉토리 명이 바뀌어 새로 다운로드 받을 수 있도록 합니다.
예) "/js/lib/mymodule.js" ➡️ "/v12/js/lib/mymodule.js"
spring.resources.chain.strategy.fixed.enabled=true
spring.resources.chain.strategy.fixed.paths=/js/lib/
spring.resources.chain.strategy.fixed.version=v12
※ 위의 방법은 Thymeleaf와 FreeMarker에서는 Spring Boot의 기본 ResourceUrlEncodingFilter가 자동으로 처리해 주지만,
일반적인 JSP 사용 시에는 별도의 ResourceUrlProvider를 사용해야 합니다.
🛠️ 개선된 해결책
JSP를 기본 템플릿으로 사용하는 환경에서는 별도의 ResourceUrlProvider 의 선언 및 추가 설정이 필요합니다.
※ properties 설정은 하지 않아도 됩니다.
1. ResourceHandler 추가
WebMvcConfigurer에 Version 전략 Resource handler를 추가합니다.
1) Hash 값
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/r/**")
.addResourceLocations("classpath:/static/")
.resourceChain(true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
}
2) 고정 버전 관리
sacle.resources.chain.strategy.fixed.version=202212_01
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Value("${sacle.resources.chain.strategy.fixed.version}")
String resourcesChainStrategyFixedVersion;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/r/**")
.addResourceLocations("classpath:/static/")
.resourceChain(true)
.addResolver(new VersionResourceResolver().addFixedVersionStrategy(resourcesChainStrategyFixedVersion, "/**"));
}
}
2. JSP 접근 허용
JSP에 정적 리소스에 접근할 수 있는 @ModelAttribute 를 추가합니다.
@ControllerAdvice
public class ResourceUrlAdvice {
private final ResourceUrlProvider resourceUrlProvider;
public ResourceUrlAdvice(ResourceUrlProvider resourceUrlProvider) {
this.resourceUrlProvider = resourceUrlProvider;
}
@ModelAttribute("urls")
public ResourceUrlProvider urls() {
return this.resourceUrlProvider;
}
}
3. JSP 적용
실제 적용할 정적 리소스의 URL 부분을 위에서 설정한 @ModelAttribute 를 이용하여 수정합니다.
기존
<script src="/r/v2/js/assets/common/common.js?ver=${useDate}"></script>
<script src="/r/v2/js/assets/common/commonApi.js?ver=${useDate}"></script>
변경
<script src="${urls.getForLookupPath('/r/v2/js/assets/common/common.js')}"></script>
<script src="${urls.getForLookupPath('/r/v2/js/assets/common/commonApi.js')}"></script>
※ 바꾸기 정규표현식
원본: "([A-Za-z0-9/.-]+)?ver=${useDate}"
변환: "${urls.getForLookupPath('$1')}"