사이트도 업데이트를 하나요? 최신버전 안내하기

2025. 8. 21. 19:07·자바스크립트/Node.js
우리 솔루션 사용자들은 대부분 브라우저를 하루 종일 켜 둔 채, 새로고침 없이 업무를 이어갑니다. 그런데 사용 중 배포가 이뤄지면 브라우저(프론트엔드)는 구버전, 백엔드는 최신 버전 상태가 되어 저장 실패나 화면 오류가 자주 발생했습니다. 결과적으로 매 배포 직후 “화면이 안 넘어간다”, “저장이 안 된다” 같은 문의가 쏟아졌고, CS팀에서는 “강력 새로고침을 해보셨나요?”라는 안내로 분주했습니다.
그래서 저는 브라우저가 열린 상태에서도 새 버전 배포 여부를 감지하고, 구버전을 사용중이라면 팝업을 띄워 강력 새로고침을 유도하는 기능을 도입하기로 했습니다. 이 글은 그런 문제의식에서 출발했고, 실제로 어떤 방식으로 적용했는지, 또 구현 과정에서 어떤 선택지들을 고민했는지 정리해 공유하려고 합니다.

 

결론

해결 방법이 궁금하신 분들을 위해 결론부터 정리하자면,

  1. 15분에 한번씩 “캐시되지 않은 version.json”파일을 불러와 최신 버전을 확인합니다.
  2. 현재 사용중인 버전을 확인하기 위해 브라우저에서 버전 식별자를 분리해 줍니다.
  3. 1과 2가 다른 경우 강력새로고침 안내 팝업을 띄워줍니다.

 

버전 식별자란?

개발자 모드에서 Elements를 확인해보면, 따로 설정한 적이 없는데도 js나 css 등의 파일명에 ?v=1.2.3 같은 버전 정보나 main.abcd1234.js 같은 해시가 붙어 있는 것을 볼 수 있습니다. 이런걸 버전 식별자(Version Identifier)라고 합니다.

버전 식별자는 브라우저 캐시에 오래된 파일이 남지 않게 하기 위해 “이건 어떤 버전의 파일이다” 라고 알려주는 역할을 합니다. 버전 식별자는 보통 세 가지 종류가 있습니다.

버전 식별자 종류

  1. 쿼리 스트링(Query String)
    • `"/styles.css?v=1.2.30"`
    • 버전을 붙여서 생성합니다 (개발자가 직접 버전을 붙여 관리하거나, package.json의 버전 정보를 활용)
    • 일부 CDN이나 프록시 캐싱에서는 무시되는 경우가 있습니다
  2. 파일명 해싱(Filename Hashing)
    • `"/main.abcd1234.js"`
    • 파일 내용을 기반으로 해시값을 붙이고, 빌드 시 자동 생성됩니다.
    • 실제 내용이 변경된 경우에만 파일명이 바뀌므로, 가장 권장되는 방식입니다.
  3. 빌드 버전/ 타임스태프 삽입
    • `"/main.js?build=20250818123045"`
    • 배포 시점의 빌드 넘버나 타임스탬프를 붙이는 방식입니다. (빌드 도구가 자동으로 타임스탬프나 빌드넘버 입력. 배포 시마다 무조건 새로운 값 생성)
    • 내용 변화가 없어도 매 배포마다 캐시 무효화가 발생하는 단점이 있습니다.

프로젝트에 적용한 과정

1. 배포할 때 버전 자동으로 입력해주기

우리 프로젝트는 타임스태프 방식으로 버전 식별자를 생성하고 있었습니다. 하지만 다들 버전 이름으로 배포를 식별하고 있었기 때문에, FE도 쿼리스트링 방식으로 바꾸고 진행했습니다.

버전 정보는 배치파일로 도커 이미지를 생성할 때, 버전 명명 규칙에 따라 생성한 새로운 버전명을 배치파일 상단의 변수에 넣습니다. 이 변수값을 BE빌드 시 넣고있는데, 이 방법을 활용해서 FE의 버전도 따로 수정할 필요가 없도록 합니다. 매 빌드 시마다 프로젝트의 코드에서 버전을 수정해달라고 하면 다들 귀찮아 할테니까요.

//빌드.bat
set VERSION=**버전명**
cd /d Z:...작업폴더_경로\\front
call npm run build --buildversion=%VERSION%

 

기존 `npm run build` → `npm run build --buildversion=버전변수`

이렇게 수정하면 빌드 시 전달된 버전명을 vue.config.js에서 읽어와 빌드 결과에 반영하게 됩니다.

이제 여기에 코드를 추가해서 public/version.json도 자동으로 수정하도록 하겠습니다. 해당 경로에 작성하면 url을 통해서도 직접 접근해 확인할 수 있습니다.

version.js 등을 별도로 생성하는 프로젝트도 있을 수 있으므로 회사의 기존 코드를 확인해서 수정해줘야 합니다.

// vue.config.js

// version.json 수정
const fs = require("fs");
const versionFilePath = path.join(__dirname, "public", "version.json");
try {
    fs.mkdirSync(path.dirname(versionFilePath), { recursive: true }); // version.json 파일 없으면 생성
    const versionJson = { version };
    fs.writeFileSync(versionFilePath, JSON.stringify(versionJson, null, 2), "utf8");
} catch (e) {
	console.error("version.json 파일 갱신 실패:", e);
}

 

이렇게 배포하면, 브라우저에서 `사이트주소/version.json`을 입력하면 버전 정보를 바로 확인할 수도 있습니다.

 

2. 버전 식별자를 이용해 현재 사용중인 버전 정보를 얻어냅니다

이게 버전 식별자!

 

위에서 언급한 것처럼, JS나 CSS 같은 정적 리소스 파일 이름에는 버전 식별자라고 부르는 값이나 해시 값이 붙어 있습니다. 따라서 현재 페이지가 로딩한 파일의 이름을 확인하면, 버전 식별자를 보고 사용자가 지금 어떤 빌드 버전을 쓰고 있는지를 알 수 있는거죠.

이걸 얻어내는 방법에는 크게 두 가지가 있습니다.

우리 프로젝트의 경우에는 쿼리 스트링 방식 버전 식별자(.js?v=1.2.30처럼)만 확인하면 되었기 때문에, 파일명에서 직접 버전을 파싱하는 방법을 채택했습니다.

// 사용중인 파일에서 버전 식별자 얻어내는 함수
function checkUsingVersion() {
  //script 파일에서 버전 정보를 가져옴
  const scripts = document.getElementsByTagName("script");

  for (let script of scripts) {
    const src = script.getAttribute("src");

    if (src && src.includes("app.js") && src.includes("?version=")) {
      const versionParam = src.split("?version=")[1]; 
      return versionParam;
    }
	// ☑️ 버전 식별자가 ?version=으로 시작할지, ?v=로 시작할지 등등은 설정마다 다릅니다. 
	//    직접 개발자모드를 켜서 확인하거나, vue.config.js 등의 빌드 설정 파일을 확인하세요!
  }
}

 

3. 1과 2를 비교해줍니다.

비교는 이 두 값을 setInterval을 실행해 비교합니다. 거래처(병원) 특성을 고려하여 점심 1시나 저녁 8시에 배포를 진행하는데, 15분 정도 간격이라면 서버 부하도 크지 않고 버전 충돌로 인한 불편함이 발생하기 전에 감지할 수 있다고 판단했습니다.

단, 브라우저가 탭 비활성화 상태일 때는 `setInterval`의 주기가 지연될 수 있다는 점은 고려해야 합니다.

//App.vue
...
mounted() { // mounted에 작성합니다!
    const TIME_15MIN = 900000; // 15분
    if (process.env.NODE_ENV == 'production') { // 개발중일땐 확인하지 않습니다
      setInterval(() => {
        const usingVersion = checkUsingVersion();

	// ☑️ 불러올 파일명 뒤에 "cache_bust=현재시간"을 붙이면 캐싱되지 않은 새로운 파일을 받아옵니다.
        fetch(`/version.json?cache_bust=${Date.now()}`) 
          .then(res => res.json())
          .then(({ version: latestVersion }) => {
            if (usingVersion !== latestVersion) {
              // ...여기에 버전이 다를 경우 진행할 로직이 들어갑니다
            }
          })
          .catch(err => {
            throw new Error(err);
          });
      }, TIME_15MIN);
    }
}

 

강력새로고침 - 코드로 하는 법

새 버전을 감지했다고 바로 새로고침을 해버리면, 의사가 작성중이던 진료 기록이 사라지는 등의 문제가 발생할 수 있습니다. 따라서 사용자의 선택에 따라 다음 알림을 띄워 새로고침을 미루거나 지금 새로고침 할 수 있도록 했습니다.

강력 새로고침(CTRL+Shift+R)은 브라우저 캐시를 무시하고 서버에서 모든 파일을 다시 받아오는 방식입니다. 개발자들은 강력 새로고침의 개념을 알지만, 일반 사용자들은 잘 모르므로 단축키가 아닌 “버튼”을 통해서 할 수 있도록 했습니다. 버튼 클릭 시 아래 로직이 실행됩니다. 흔히 사용하는 `window.location.reload(true)`는 원하는 대로 완벽한 새로고침이 되지 않았습니다. 대부분의 최신 브라우저에서 무시되는 오래된 방식이기 때문입니다. 저는 vue와 리액트에서 제공하는 서비스워커를 활용해서 새로고침을 진행했습니다.

if ('serviceWorker' in navigator) { // 서비스워커가 있는 경우

  (async () => {
    try {
      const cacheNames = await caches.keys();
      await Promise.all(cacheNames.map(name => caches.delete(name))); // 모든 캐시 삭제

      const registrations = await navigator.serviceWorker.getRegistrations();
      await Promise.all(registrations.map(registration => registration.unregister())); // 서비스워커 등록 해제
      
    } catch (error) {
      // 에러처리 : 팝업을 띄워도 되고, 다른 방식으로 시도해봐도 되고요..!
    } finally {
      window.location.reload(); // 캐시 비운 뒤 새로고침	    
    }
    
  })();
  
}else{
  // ☑️ 모종의 이유로 서비스워커를 사용할 수 없는 프로젝트는 이렇게 작성합니다. 강력 새로고침은 되지만 url이 지저분해져요
  setTimeout(() => {
    const url = new URL(window.location.href);
    url.searchParams.set('forceReload', Date.now()); // 현재 시간 고유값
    window.location.href = url.toString();
  }, 0);
}

 

 

시행착오 모음..

어이구 참내

 

검색이나 챗GPT에서 추천하는 방식을 이것저것 시도해 봤지만, 알고 보니 ‘페이지에 새로 접속해도 캐싱된 파일을 불러오는 문제’만을 해결하는 방법인 경우가 많았습니다. 우리 문제는 사용자가 아예 새로 접속하지 않고 계속 켜둔 상태에서 발생했기 때문에 맞지 않았지만, 알아두면 좋은 내용들이 많아 함께 정리합니다.

  1. 빌드 시 파일명 해싱 사용
    • 모든 정적 자원을 main.abc123.js처럼 해시로 빌드하면 브라우저가 캐시된 파일이 아닌 새로운 파일을 받아옵니다.
    • 대부분의 빌드 도구(Vue CLI, Webpack)는 기본적으로 지원합니다.
  2. index.html의 Cache-Control 헤더를 no-cache로 설정해 항상 서버에서 확인하게 합니다.
    • index.html이 캐싱되어 오래된 파일만 불러오고 있다면 새로운 해시 번들 정보를 못 가져오기 때문입니다.
    • 배포하는 서버(애저, AWS 등)에서 헤더 정보를 설정해 주는 방식이 보편적입니다.
      //nginx 기준
      location /index.html {
          add_header Cache-Control "no-cache, must-revalidate";
      }
  3. 서비스 워커를 활용해 새 버전을 감지하고 반영합니다. (리액트, vue에서 사용 가능하지만 서비스 워커 기반 업데이트는 UX 설계 여하에 따라 의도치 않은 순간에 리로드가 발생할 수 있어 잘 확인해야 합니다)
import { register } from 'register-service-worker'

if (process.env.NODE_ENV === 'production') {
  register(`${process.env.BASE_URL}service-worker.js`, {
    updated() {
      // 새 버전이 있으면 알림 띄우고 강제 새로고침
      window.location.reload(true)
    },
  })
}

 

 


후기

이번에도 Node.js와 Vue 빌드 과정을 이것저것 살펴보면서 많은 것을 배웠습니다. 예전에 자바 스프링부트 서버 실행이 잘 안 되었을 때는 답답하고 화가 났었는데, 확실히 프론트엔드 쪽의 고민은 재미있었습니다.

기능 도입 이후 3-4번의 배포가 있었는데 실제 서비스에서도 팝업이 잘 떴고, 버튼 클릭 시 새로운 버전이 반영되는 것도 확인할 수 있었습니다. 강력 새로고침을 하지 않아 CS팀으로 유입되던 민원이 발생하지 않는 것을 보니 뿌듯했습니다. 원래는 결과물이 바로 나오는 코딩이 재밌었는데 효과가 이렇게 가시적으로 나오니 재미있어서 최적화 쪽도 더 파보고 싶은 마음이 들었습니다!

 

 

 

728x90
저작자표시 동일조건 (새창열림)
  • zenna
    zennaUniverse
    zenna
    • 분류 전체보기
      • 프로젝트 & 활동
        • 항해99
        • 코딩일기
      • 자바스크립트
        • 바닐라 JS
        • Vue
        • React
        • Svelte
        • Node.js
      • HTML, CSS
      • DB
      • 개발 팁
      • ▶ 유용한 툴
      • 인프라 & 서버
      • Git, GitBash
      • 그 외 언어
        • Java
        • Spring,Servlet
        • Python
        • Django
        • 머신러닝
        • 안드로이드, 코틀린
      • 코테
  • 인기 글

  • 최근 글

  • 링크

    • git hub
  • 전체
    오늘
    어제
  • 300x250
  • hELLO· Designed By정상우.v4.10.5
zenna
사이트도 업데이트를 하나요? 최신버전 안내하기
상단으로

티스토리툴바