본문 바로가기
tech epilogue

ChunkLoadError 해결기

by rami_ 2026. 5. 15.

최근 개발서버에 로그인 시 아무런 액션이 일어나지 않는 다는 이슈가 인입되었습니다.

해당 프로젝트는 S3와 CloudFront로 조합으로 배포된 CSR(Client SIde Rendering) 환경이었습니다.

원인을 찾으려고 개발자 도구로 콘솔을 켜보니 ChunkLoadError가 나고 있었습니다.

 

원인은 배포 타이밍과 유저의 접속상태가 어긋났기 때문이었습니다. 구버전 페이지를 켜놓았던 사용자가 브라우저를 그대로 둔 채로 제가 신버전을 배포했고, 그 이후에 사용자가 페이지를 이동하거나 특정 액션을 취하면서 S3에서 이미 지워진 이전 청크 파일을 요청하게 된 것이죠.

 

1. 해결책: 인프라와 코드 레벨에서 방어선 구축하기

이 문제를 해결하기 위해 두 가지 관점에서 보완책을 적용했습니다.

 

① 인프라 레벨: S3 배포 시 구버전 파일 유지

기존 CI/CD 파이프라인에서는 S3로 빌드 파일을 업로드할 때 기존 파일들을 다 지우고 새로 올리는 방식을 쓰고 있었습니다.

이 부분을 개선하기 위해 새 빌드 파일을 올릴 때 기존 파일을 지우지 않는 aws s3 sync 명령어를 사용하거나, 기존 파일을 유지한 채 덮어쓰도록 설정을 바꿨습니다. 이렇게 하면 S3에 구버전 청크와 신버전 청크가 같이 남아있게 됩니다. 이미 접속 중인 구버전 사용자가 페이지를 이동하더라도 S3에서 구버전 청크를 정상적으로 다운로드할 수 있게 됩니다.

 

② 코드 레벨: 에러 핸들링과 강제 새로고침

인프라에서 파일을 유지해 주더라도 만약의 상황이 있을 수 있으니, 코드 레벨에서 ChunkLoadError를 캐치했을 때 페이지를 새로고침(window.location.reload())하도록 예외 처리를 추가했습니다. 덕분에 에러가 나더라도 사용자가 자연스럽게 최신 버전의 소스코드를 바라보게 됩니다.

 

2. 빌드 파일 생성과 콘텐츠 해시(Content Hash)의 원리

이 이슈를 해결하면서 빌드 과정에서 파일들이 정확히 어떻게 만들어지고, 내부 구조는 어떻게 짜이는지 다시 한번 정리해 보았습니다.

우리가 npm run build 명령어로 빌드하게 되면 컴포넌트와 소스코드가 브라우저가 해석할 수 있는 몇개의 정적 파일로 변환됩니다. 이때 Webpack, Vite, Rollup과 같은 빌드도구는 성능 최적화와 캐싱을 위해 독특한 규칙으로 파일 이름과 구조를 만듭니다.

 

 일반적으로 CSR프로젝트를 빌드하면 이런 구조로 파일이 생성됩니다(Vite 기준).

dist/
├── index.html               # 서비스의 유일한 진입점 (HTML 소스)
└── assets/                  # 자바스크립트, CSS, 이미지 등 정적 자원 모음
    ├── index-v8e7d2a1.js     # 메인 실행 로직 (번들 파일)
    ├── index-b4f2e9a3.css    # 메인 스타일시트
    ├── AboutPage-c3b4a5d6.js # 지연 로딩(Lazy Loading)된 특정 페이지 청크
    └── logo-d7e8f9a0.png     # 해시값이 붙은 이미지 자원

 

빌드된 자바스크립트나 CSS 파일 이름을 보면 중간에 v8e7d2a1 같은 문자열이 붙어 있는데, 이걸 콘텐츠 해시(Content Hash)라고 부릅니다.

빌드 도구는 파일의 내용물(코드)을 바탕으로 고유한 암호값을 만들어 파일 이름에 붙여줍니다.

  • 코드가 바뀌지 않았다면: 다음 빌드에서도 이름이 똑같이 유지되어서 브라우저 캐시를 적극적으로 활용합니다.
  • 코드가 조금이라도 수정되었다면: 새로운 해시값으로 바뀌면서 파일 이름이 변경됩니다. 브라우저는 이름이 바뀐 걸 보고 서버에서 새 파일을 다운로드하게 됩니다.

 

3. 파일 변경을 감지하는 구체적인 기준

여기서 '코드가 수정되었다'는 것은 해당 빌드 파일을 만드는 데 들어간 모든 소스코드 중 단 한 글자, 혹은 설정 하나라도 바뀐 경우를 말합니다. 구체적으로 아래와 같은 변경 사항들이 해당됩니다.

 

1) 컴포넌트 내부의 비즈니스 로직 및 텍스트 변경

  • 개발자가 코드를 수정하는 대부분의 행위가 포함됩니다.
  • 주석은 제외될 수 있습니다. 빌드 도구가 최적화(Minify) 과정에서 주석을 다 지워버리기 때문에, 단순 주석만 추가하거나 삭제한 경우에는 최종 결과물 코드가 똑같아서 해시가 바뀌지 않습니다.

2) 의존성(Dependency) 변경

  • package.json에서 외부 라이브러리 버전을 올리고 빌드하면, 라이브러리 코드가 합쳐지는 번들 파일(보통 vendor.js나 메인 index.js)의 내용이 바뀌기 때문에 해시가 새로 생성됩니다.

3) 환경변수 및 빌드 설정의 변경

  • .env 파일에서 환경변수 값을 바꾸면, 빌드 도구가 자바스크립트 파일 내부에 실제 주소 문자열 등을 주입하기 때문에 내용이 바뀐 것으로 인식합니다.
  • vite.config.ts나 webpack.config.js 등에서 압축 옵션을 바꾸거나 타겟 브라우저 버전을 바꾸면 변환된 문법 자체가 달라져서 해시가 바뀝니다.

 

4. 해시 생성 메커니즘: 눈사태 효과

Vite나 Webpack 같은 빌드 도구는 빌드 파이프라인의 최종 단계에서 코드들을 하나로 뭉치고 압축해서 최종 파일의 텍스트 내용물을 완성합니다.

그 후 완성된 텍스트 내용물 전체를 입력값으로 넣고 암호화 알고리즘을 돌려 고유한 해시 문자열을 뽑아냅니다. 이 해시는 내용물이 단 1바이트만 달라져도 완전히 다른 문자열로 바뀌는 특징이 있습니다. 그렇게 뽑아낸 해시값을 파일 이름 중간에 index-[hash].js 형태로 주입해서 저장하는 것이죠.

 


 

만약 하나의 청크 파일만 변경되면 어떻게 될까?

코드 전체가 아니라 특정 페이지나 하나의 청크만 수정되면, 해당 청크와 그 청크를 호출하는 진입점(Entry Point) 파일의 해시만 바뀝니다.

변경되지 않은 나머지 청크 파일들은 이전 파일 이름(해시값)을 그대로 유지합니다. 덕분에 브라우저는 바뀐 청크만 새로 다운로드하고, 나머지는 캐시를 사용해 효율적으로 페이지를 로드할 수 있습니다.

 

 

 

단순한 트러블슈팅으로 시작해서 빌드 도구의 최적화 원리까지 다시 한번 점검해 볼 수 있었던 유익한 경험이었습니다.