별건 아니지만, 최근 정적 파일 배포 시 브라우저 사용자가 강제 캐시 삭제를 하지 않으면 예전 버전의 사이트를 조회하게 되는 문제가 있었습니다.
이런 캐시 문제가 발생한 이유와 대처 방안에 대해 생각해보니
원인은 크게 두 가지를 떠올릴 수 있었습니다.
request가 들어왔을 때 응답하는 response에 대해 캐시된 결과를 전달할지 말지에 대해 결정하는 주체는 누굴까요?
웹서버 입니다.
다시 말하면 리퀘스트를 처리하는 서버입니다. 그 용도가 웹을 서빙하는거면 웹 서버겠죠?
저는 nginx를 웹서버로 사용하고 있는데
nginx 웹 서버의 기본 cache 설정은 24시간 입니다.
특정 요청에 대한 응답 리스폰스 헤더를 열어보면 last-modified
값과 date
값이 있는데, last-modified
는 nginx가 요청된 파일에 대한 최신본을 확인한 시간입니다.
date
값은 실제 응답을 전달한 시간입니다.
두 시간 차이가 크지 않으니 캐시가 일어난것이죠.
nginx와 같은 웹 서버 소프트웨어를 이용해 배포 환경을 구성하게 되면,
요청이 들어올 때 아래와 같은 과정을 거칩니다.
client(browser) -> ?? -> web server -> client(browser)
여기서 ??
부분은
CDN (content delivery network)와 같은 기능을 사용해 웹 서버의 캐싱 기능 적용 이전에 ?? 에서 캐시 리스폰스 처리를 해버릴 수 있는 신원 미상의 물체이기 때문에 ??로 표시했습니다.
CDN이 아니더라도 중간에 proxy 서버를 거친다고 한다면 이 캐시 문제가 어디서 발생했는지 추측하는게 굉장히 애매해집니다.
다행히도 중간 과정 ??
에서 발생한게 아니라는 것을 알고 있었고,
리엑트로 작성한 JS 번들 정적 파일의 해쉬 값이 변경되지 않는 것을 확인했습니다.
뭔말인고 하면 저 해쉬 값으로 명명된 청크파일들은 nginx에서 '정적파일'로 처리하는 부분입니다. n 번의 빌드 시에 저 해쉬 값이 동일하게 되면, 배포가 이뤄져도 웹서버가 기존 캐시를 날리고 최신 파일로 교체해야 하는지 알 수가 없는거죠.
웹 서버 설정을 통해 강제로 캐싱이 안되게 할 수 있는데요,
nginx와 같은 웹서버를 사용하는 이유 중 정적 파일의 캐싱으로 First Time To Byte 시간을 최소화하는 것도 있습니다. 캐싱 사용을 무효시키는 것은 쥐한마리 잡겠다고 초가삼간 태우는 거라고 할 수 있습니다.
nginx에서 서빙하는 파일은 js 파일만 있는게 아니라 이미지, css등등 매우 다양하기 때문입니다.
캐싱 기능을 끄는 것은 해결방안 후보지에서 제외하고 그럼 웹서버가 캐싱을 할지 안할지 어떻게 구분하는지에 대해 집중해보면 방안이 보입니다.
aws의 s3는 CDN 기능을 제공하는데, 이 때 s3에 담긴 파일의 경로는 곧 s3 서버에 담긴 각 정적파일들을 폴더 구조와 같이 보이게끔 /
표시로 구분을 한 것입니다.
s3/static/123.js 파일을 요청한다고 할 때
캐싱이 되지 않고 새로운 값을 요청하고 싶으면
s3/static/123.js?new=123
과 같이 요청 url의 쿼리 파라메터를 다르게 한다든가 요청 url의 포맷을 다르게 하는 것이죠.
이런 원리를 사용해 외부 서버를 통해 리스폰스를 받아와야 할 때나 특정 서버를 한 다리 걸쳐서 서비스를 서빙해야 할 때는 정적 파일 빌드 시 이름을 랜덤하게 정해주면 됩니다.
만약 프로젝트를 Creact react app 보일러플레이트를 사용하지 않고 웹팩과 같은 번들러를 이용해 손수 환경을 세팅했다면 이런 문제는 일어나지 않았을 확률이 높습니다.
왜냐면 CRA는 항상 빌드 시 동일한 이름으로 정적 파일 이름을 짓거든요.
eject를 하지 않고 CRA 환경의 웹팩 설정을 커스터마이징 할 수 있게 도와주는 라이브러리가 있습니다. rewired라고 하는 라이브러리인데요,
설치 후 config-overrides.js
파일 내부에 다음 처럼 함수를 정의해줄 수 있습니다.
module.exports = {
webpack: function (config, env) {
config.output.filename = 'static/js/[name].[hash:8].js';
config.output.chunkFilename = 'static/js/[name].[hash:8].chunk.js';
return config;
},
};
이를 통해 각 빌드마다(코드 변경이 일어난) 다른 정적 파일 해쉬를 만들어 주어 문제를 해결했습니다.
이번 시간에는 별거 아닌 문제지만 자칫 잘못하면 별게 될 수 있는 문제를 해결한 썰을 풀어봤습니다.
다음에는 더 유익한
내용을 가져오도록 노력해보겠습니다.
감사합니다.
CRA에서는 build 시에
https://create-react-app.dev/docs/production-build
hash를 붙여주고 있습니다.
엄청 별거 맞는거 같은 글,, 잘 읽었습니다~