웹 개발자이면서 브라우저 캐시에 대해 잘 몰랐다. 그냥 브라우저가 다 알아서 해주는 거겠지
라고만 생각했었다.
진행하고 있는 프로젝트는 리액트로, 시간이 지나면 지날수록 사이즈가 커짐에 따라 webpack 으로 bundle 한 결과물의 사이즈도 점점 커져만 갔다.
이에 따라 최적화를 위해 여러가지를 적용했다.
트리 쉐이킹, 비동기 import 청크, 공통 청크 등등..
그럼에도 불구하고 청크 수가 너무 많으면 그것 또한 문제이고, node_modules
의 패키지를 묶어놓은 vendor
는 청크하지 않는 것이 웹팩 설정 파일을 깔끔하게 유지하는 것 같아 그렇게 하진 않았다.
그러던 찰나, 생각해보니 브라우저 캐시가 작동하지 않는 것 같았다. 매번 resources 를 네트워크에서 새로 받는 것이었다. 파일 이름과 내용이 변하지 않았음에도 불구하고 그랬다.
당연히 브라우저 캐싱이 되어야 한다고 생각했는데, 그게 아니었다.
크롬의 개발자도구를 열어 Response Header 를 살펴보니 Cache-Control
헤더가 no-cache no-store must-revalidate
의 값을 갖고 있었다.
이건 내가 설정한 게 아닌데 어디서 온걸까? 란 질문으로 시작하여 찾아보니 Spring MVC 설정 인터페이스인 WebMvcConfigurer
(spring 4 이전까진 WebMvcConfigurerAdapter 클래스) 에서 캐시관련 설정을 할 수 있는 것을 알게 되어 아래와 같이 적용하였다.
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/{path:[\\w\\-]+}")
.setViewName("forward:/");
registry.addViewController("/**/{path:[\\w\\-]+}")
.setViewName("forward:/");
} // 그렇다, 스프링 애플리케이션 안의 프론트 엔드 렌더링을 템플릿 엔진이 아니라 SPA인 React로 Serve 하고 있다.
// ... other configurations
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("classpath:/static/", "/bundle/")
.setCacheControl(CacheControl.maxAge(14, TimeUnit.DAYS));
}
}
그래서 이제 잘 되겠지! 라고 생각했는데, 여전히 Cache-Control 헤더의 값은 변하지 않았다.
또 찾아봤다.
스프링 시큐리티 Spring Security 가 WebMvcConfigurer
를 통해 설정한 캐시 설정을 덮어씌운다는 것을 알게 되었다.
기존에
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
의 어노테이션들이 달린 설정 클래스가 있었는데, 이게 문제였던 것이다. 그러나, 이것을 없앨 순 없다.
workaround 로 해결가능한 방법은 특정 ant pattern과 일치하는 요청에 대해서는 덮어 쓰지 않도록 시큐리티 설정을 무시하는 설정이었다.
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
// ... other fields and constructor
@Bean
@Profile({"default", "dev", "local", "stg", "prod"})
public WebSecurityConfigurerAdapter forDevelopment() {
return new WebSecurityConfigurerAdapter() {
@Override
protected void configure(HttpSecurity http) throws Exception {
// ... other configurations
}
@Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity.ignoring().antMatchers("/bundle/**"); // 요놈이 핵심
}
};
}
이제 브라우저 캐시가 잘 동작한다.
웹팩의 결과물인 resources 만 브라우저가 캐시하도록 하는 것이 내 목적이었으므로 이 방법으로 해결되었다.
Comments