[React] 배포 후 간헐적으로 개발 서버에서 화면 에러가 뜨는 이유

2025. 10. 14. 10:06·Development
기록용으로 찾아보며 정리한 글로, 해당 내용에 대해 틀린 내용이 있을 수 있습니다! 틀린 내용은 지적해 주시면 감사하겠습니다:)

개요

개발 서버에 배포하면 간헐적으로 모든 라우터에서 에러바운더리가 발생하고 화면이 뜨지 않는 문제가 종종 발생하였다.

???

새로고침 시나 라우터 이동 으로는 해결되지 않고 모든 페이지에서 발생했다. 그리고 브라우저에서 캐시 비우기 및 강력 새로고침을 하면 정상 동작하였다. 강력 새로고침만 했을 때는 동작하지 않음.

즉, 캐시 비우기가 핵심인데 왜 이런 건지? 왜 개발 서버에서만 발생하고 로컬 환경에선 괜찮은 건지 이유가 궁금했다.

  • 강력 새로 고침: `Cmd + Shift + R`

 

예를 들어 스타일시트 수정이나, 이미지 수정은 크롬 브라우저에서 일반 새로고침 시에 캐시 때문에 반영이 안 되는 경우가 많다. 

이럴 경우 캐시 비우기를 해줘야 한다.

 

원인

리액트 특성상, 배포하고 나면 새로고침은 꼭 필요하다. 하지만 현재 현상은 캐시 비우기가 동반되어야 하므로, 단순 배포 이후 새로고침과는 다른 문제라고 생각했다.

흠

 

 

빌드와 배포

코드를 수정하고 새로 배포하면, 웹팩(Webpack)이나 비트(vite) 같은 빌드 툴은 변경된 내용을 반영해서 `main.a1b2c3.js`, `chunk.x4y5z6`처럼 해시값이 포함된 새로운 파일 이름으로 결과물을 만든다. 그리고 `index.html` 파일은 이 새로운 파일들을 연결하도록 업데이트된다.

 

브라우저의 캐싱 동작

  • 사용자가 업데이트된 사이트에 접속하면 브라우저는 일단 `index.html`을 서버에 요청한다.
  • 서버는 새로운 `index.html`을 보내준다. 이 파일 안에는 "이제부터 `main.a1b2c3.js`파일을 사용해!"라고 적혀있다.
  • 브라우저는 이 지시에 따라 `main.a1b2c3.js`을 요청해야 하는데, 여기서 문제가 발생한다. 개발 서버가 JS, CSS 같은 정적 파일(static assets)에 대해 "이 파일들은 아주 오랫동안 캐시(저장)해놓고 재사용해"(`Cache-Control: max-age=31536000` 등)라는 강력한 캐싱 정책을 설정해 둔 경우, 브라우저는 서버에 새 파일을 요청하지 않고 자신이 가지고 있던 구 버전 `chunk.xxxx.js` 파일을 그냥 사용해 버린다.

 

에러발생

결국 브라우저는 새로운 `index.html`과 `main` 파일을 기반으로 구버전 코드 조각(chunk) 파일을 실행하게 된다. 이 코드 조각들은 서로 버전이 맞지 않기 때문에, 함수나 변수를 찾지 못해 `TypeError: vt is not a fuction` 같은 에러를 뿜어내며 앱 전체가 멈추는 것이다.

캐시 비우기 및 강력 새로고침이 해결책인 이유는 이 동작이 브라우저에 저장된 모든 버전의 JS, CSS 파일을 강제로 삭제하고 서버로부터 전부 새로 다운로드하게 만들기 때문이다. 일반적인 "강력 새로고침"은 `index.html`만 새로 가져오고 나머지 파일은 캐시를 재활용할 수 있어 문제가 해결되지 않는 것이다.


console error

배포 이후에 웹이 바라보는 청크 파일과, 실제 청크파일이 달라서 번들링 된 js를 못 잡는 오류로 보인다.

 

핵심 요약

증상 원인

  • HTML(= `index.html`)이 캐시에 남아 오래된 해시 청크를 가리킴 -> 새 배포에서 해당 청크 파일이 삭제됨
  • Nginx가 바라보는 디렉터리를 빌드 중에 비움 -> 순간적으로 정적 파일이 사라져 전 라우트에서 에러

 

정상 동작 원리

  • 해시 붙은 정적 자산 (JS/CSS/dlalwl): 오래 캐시
  • HTML(`index.html`): 항상 최신을 받아야 함 (no-store/짧은 TTL)
  • "현재 열려 있는 탭"은 구버전으로 계속 돌아가도 깨지면 안 됨 -> 구버전이 참조하는 청크 파일을 바로 지우지 말고 일정 기간 보존하거나, 청크 로드 실패 시 전체 리로드 로직을 둔다.

 

왜 로컬에서는 괜찮고, 개발 서버(Nginx)에서만 터지나?

  • 로컬 Vite dev 서버는 해시 청크 + 삭제가 없다(개발 시 HMR/ESM). HTML도 항상 최신.
  • 개발 서버는 정적 파일 + 캐시 정책 + 배포 타이밍의 영향을 받는다. 특히 HTML이 캐시 되면 구 해시를 계속 가리킨다.

 

Vite는 기본적으로 빌드하면, 웹팩과 다르게 dist 폴더를 모두 비우고, 다시 쌓는다.

nginx는 보통은 해당 dist를 바라보고 있어서, 배포하는 사이에 비어져서 웹이 터지는 것으로 추정된다.

 

Vite에서 이를 위한 설정은 딱히 없고, preview 기능으로 된다고는 하는데 이 부분은 확인이 필요하다.

다른 개발자에게 물어보니 쉘 스크립트를 만들어서 dist -> 심볼릭 링크로 릴리즈 폴더를 바라보게 해서 해결했다고 한다.

(nginx는 dist 바라보고, dist는 릴리즈 폴더를 바라보는 구조)

더보기

배포 시작

-> 릴리즈 폴더에 새로운 배포 파일 쌓기
-> 배포 완료되면 dist가 바라보는 릴리즈 폴더를 신규 릴리즈 폴더로 변경
-> 이전 릴리즈 폴더 삭제(버전관리 용으로 n 개씩 남겨도 됨)

 

이를 위해 릴리즈 폴더를 따로 만들고, 릴리즈 폴더 내에는 빌드 결과물만 쌓아둔다고 한다.

 

리액트는 `index.html`이 있다. (엔트리 포인트)

이 `index.html` 내부에 <script> 태그로 화면을 리액트가 그려준다.

이게 SPA 기본 골조인데, 리액트는 태생적 한계가 빌드 시 청크 파일이 나오는데, 청크파일명은 hash값으로 들어가고 `index.html`이 바라보는 js파일은 `asd.js`를 바라보고 있는데 배포하면 해쉬값 바뀌니 `edf.js`로 파일이 내려지는 거고 그럼 `asd.js`를 못 찾으니 저 함수들 못 찾는다고 언디파인드 에러가 뜬다.

spa는 배포하면 새로고침은 어쩔 수 없는 한계이다.

정말 무중단 자동배포 적용을 하려면 SSR 써서 각 엔트리포인트 `index.html`을 따로 만들어야 한다.

  • SPA의 무중단 배포 : 배포해도 웹이 다운되지 않고 프로세스는 지속 단 배포 시 새로고침 해야 배포 내용 적용
  • SSR의 무중단 배포 : 배포해도 웹이 다운되지 않고 프로세스는 지속 단 배포 시 index를 다시 받는(메뉴를 닫았다가 다시 들어가는 행위) 발생 시 배포 내용 적용

 

참고

더보기

https://adjh54.tistory.com/70

 

[React] 캐시 무효화(Cache Busting) 이해하고 적용하기

해당 글의 목적은 캐시 무효화란 무엇이고 어떠한 해결 방법이 있는지에 알아보기 위한 목적으로 글을 작성하였습니다. 1) 문제점 발생💡 웹 브라우저에서 새로 배포를 하였는데도 이전 파일을

adjh54.tistory.com

https://velog.io/@goon126/%EC%B2%AD%ED%81%AC-%EC%97%90%EB%9F%AC

 

[React] 청크 로드 에러 해결하기 (Loading chunk failed)

지난시간에는 코드스플리팅에 대해서 설명했습니다. 허나 무작정 해당 기술을 적용하게되면 이전글에서 설명했다시피 문제가 발생합니다. 바로 배포에서 문제가 발생하는데요. 해서 해당 이슈

velog.io

 

'Development' 카테고리의 다른 글

[Cursor] Playwright로 테스트부터 디버깅까지 한번에 끝내기  (0) 2025.11.11
[CS] "나누기 0"은 왜 어떤 언어에선 에러가 나고, 어떤 언어에선 무한대(Infinity)가 될까?  (1) 2025.11.03
[Web] 브라우저 “탭( Tab )”을 노리는 공격, Tabnabbing  (0) 2025.10.13
[C언어] return 0, 그리고 0과 1의 진짜 의미  (0) 2025.10.06
[macOS] port 빠르게 종료하기  (0) 2025.09.30
'Development' 카테고리의 다른 글
  • [Cursor] Playwright로 테스트부터 디버깅까지 한번에 끝내기
  • [CS] "나누기 0"은 왜 어떤 언어에선 에러가 나고, 어떤 언어에선 무한대(Infinity)가 될까?
  • [Web] 브라우저 “탭( Tab )”을 노리는 공격, Tabnabbing
  • [C언어] return 0, 그리고 0과 1의 진짜 의미
곽진돔
곽진돔
Developer
  • 곽진돔
    echo "곽박한 세상";
    곽진돔
  • 전체
    오늘
    어제
    • 분류 전체보기 (203)
      • Development (74)
        • Linux (13)
        • k8s (3)
        • Docker (5)
        • AWS (1)
        • PHP (35)
        • Python (21)
        • Java (1)
        • SpringBoot (4)
        • JavaScript (2)
        • React (10)
        • MySql (19)
        • MongoDB (1)
      • Daily (6)
      • Study (7)
        • TIL (2)
        • license (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
    • 설정
  • 링크

    • github
  • 공지사항

  • 인기 글

  • 태그

    springboot
    docker
    error
    Shell
    ssh
    인코딩
    nodejs
    Python
    리눅스
    정규표현식
    IP
    CentOS7
    윈도우
    react
    db
    스프링부트
    php
    Selenium
    Java
    UTF8
    CentOS
    MySQL
    SQL
    크롤링
    date
    Linux
    chromedriver
    JavaScript
    리액트
    HTML
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
곽진돔
[React] 배포 후 간헐적으로 개발 서버에서 화면 에러가 뜨는 이유
상단으로

티스토리툴바