Vue 배포 후 캐시로 인해 변경사항이 반영 안될 때

Vue 프로젝트는 기본적으로 SPA(Single Page Application)으로 처음에 모든 컴포넌트를 로드 시킨 다음 구동하는 방식입니다. 만약 수정 또는 신규 내용을 추가한 다음 배포를 하면 캐시 문제로 사용자 브라우저에 반영되지 않는 문제가 발생할 수 있습니다.

  • build로 배포되는 js 파일이 브라우저에 캐시 되어 있으면 이전 버전이 계속 보이기 때문에 js파일명에 hash 값을 자동으로 붙이도록 설정합니다.

    vue.config.js파일을 다음과 같이 작성합니다.

    const { defineConfig } = require("@vue/cli-service");
    module.exports = defineConfig({
      filenameHashing: true,
      configureWebpack: (config) => {
        config.output.filename = "js/[name].[hash].js";
        config.output.chunkFilename = "js/[name].[hash].js";
      },
      transpileDependencies: true,
      pluginOptions: {
        vuetify: {
          // https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vuetify-loader
        },
      },
    });
    
  • html 파일의 캐시를 명시적으로 사용하지 않도록 nginx에서 header를 설정합니다.

    location ~* \.html?$ {
        add_header Cache-Control "no-cache, no-store";
        try_files $uri $uri/ /index.html;
    }
    
  • 위 내용 적용으로 새로 고침하거나 주소창 접근 시에는 변경사항이 적용됩니다. 하지만 브라우저를 종료하지 않은 상태에서 새로고침을 하지 않은 채 router로 페이지 이동을 하여 사용할 경우 서버에서 index.html을 다시 가져오지 못합니다. 따라서 index.html을 수동으로 갱신 해줘야 합니다. 이를 해결하기 위해서는 몇 가지 방법이 있습니다.

    • 첫번째, 서버-SPA(vue) websocket을 만들고 application version 정보를 수신 받고 변경되면 window.location.reload() 하도록 합니다. 하지만 서버 인스턴스를 만들어야 되고 websocket에 대한 구현 시간이 필요하므로 일단 패스합니다.

    • 두번째, vuex에 needReload(초기값 false)라는 변수를 만듭니다. 그리고 AccessToken(보통 10분 미만)이 만료되어 갱신될 때 needReload를 true로 변경 합니다. 그런 다음 router의 afterEach 메서드에서 true 값이면 window.location.reload()를 해줍니다. 이렇게 설정하면 10분마다 한번씩 사용자가 페이지 이동을할 때 reload 됩니다.

      axios intercepter

      commonAxios.interceptors.response.use(
        function (response) {
          if (response.headers['authorization']) {
            // 서버로 부터 accessToken을 받은 경우 storage의 accessToken을 갱신한다.
            store.commit('accountStore/updateAccessToken', response.headers['authorization']);
                
            // page 이동 시 reload를 하도록 설정.
            store.commit('appStore/setNeedAppReload', true);
          }
          return response;
        },
      );   
      

      appStore.ts

      export interface AppStoreState {
        needAppReload: boolean;
      }
          
      export const appStore: Module<AppStoreState, RootState> = {
        namespaced: true,
        state: {
          needAppReload: false,
        },
        getters: {
          getNeedAppReload(state): boolean {
            return state.needAppReload;
          },
        },
        mutations: {
          setNeedAppReload(state, needAppReload) {
            state.needAppReload = needAppReload;
          },
        },
        actions: {},
      };
      

      router/index.ts

      router.afterEach((to, from) => {
        if (store.getters['appStore/getNeedAppReload']) {
          window.location.reload();
          store.commit('appStore/setNeedAppReload', false);
        }
      });
      

댓글남기기