Vue.js 3 & NodeJS/사내교육 - 점심 메뉴

[17] Frontend 개발 - Store State 유지, Cookies, localStorage, Pinia Plugin Persistedstate

carrotweb 2026. 8. 5. 19:28
728x90
반응형

페이지 새로고침 문제점

웹 브라우저에서 새로고침을 하면 Vue가 초기화 되면서 Store가 초기화 됩니다.

 

페이지 새로고침 문제점 해결

Store에 있는 State를 유지하기 위해서는 state의 정보를 저장하고 초기화되면 다시 읽어 들어 유지하게 하면 됩니다.

 

State의 정보를 저장하고 유지하기 위해 다음과 같이 세 가지 방법이 있습니다.

  • Cookie(쿠키)를 사용하는 방법
  • Storage(스토리지)를 사용하는 방법
  • Pinia Plugin Persistedstate를 사용하는 방법

 

Store State 유지 - Cookie(쿠키) 사용

 

웹 브라우저에서 F12키를 누르면 개발자 툴이 나타납니다.

여기서 Application를 클릭하고 Cookies를 클릭합니다.

 

Cookie 구성

  • Name : 쿠키 이름
  • Value : 쿠키 값
  • Domain : 도메인 (쿠키를 수신하도록 허용된 호스트)
  • Path : 경로 (쿠키 헤더를 보내기 위해 요청된 URL)
  • Expires / Max-Age : 쿠키 만료 기간 / 쿠키 최대 사용기간
  • Size : 쿠키 크기(byte)
  • HTTP : HTTP 사용 여부 - true / false (true 이면 HTTP를 통해서만 쿠키를 서버로 보낼 수 있음)
  • Secure : HTTPS 사용 여부 - true / false (true 이면 HTTPS를 통해서만 쿠키를 서버로 보낼 수 있음)
  • SameSite : 사이트 제한 설정 - none, strict(기본값), lax로 설정 (쿠키를 자사(http://www.home.com) 또는 동일 사이트(dev.home.com) 컨텍스트로 제한해야 하는지를 선언할 수 있습니다. CSRF(Cross-Site Request Forgery - 교차 사이트 요청 위조) 공격을 방지하기 위해 HTTP 쿠키에서 설정할 수 있는 속성입니다.)
    • none : 모든 도메인에 쿠키가 전송
    • strict : 동일한 사이트 내의 요청에만 전송
    • lax : 동일한 사이트 내의 요청 또는 "안전한" HTTP 메서드를 사용하는 최상위 경로(/)인 경우 전송
  • SameParty : 동일 사이트 사용 여부 - true / false (true 이면 동일한 사이트 내의 요청에서 최상위 경로(/)인 경우 쿠키를 설정하거나 전송할 수 있음)
  • Priority : 우선순위 - low, medium(기본값), high로 설정

Cookie 함수

Cookies에 저장하기

cookies.set(keyName: string, value: string, expireTimes?: string | number | Date, path?: string, domain?: string, secure?: boolean, sameSite?: string)

Cookies의 set() 메서드는 Cookies에 keyName으로 value를 저장합니다.

Cookies의 set() 메서드에서 만료 기간을 지정하지 않으면 현재 시간에서 하루(1d)가 추가되어 설정됩니다.

그리고 경로(Path)도 설정하지 않았기 때문에 최상위 경로(/)로 설정됩니다.

 

Cookies에서 가져오기

cookies.get(keyName: string)

Cookies의 get() 메서드는 Cookies에서 keyName으로 검색하여 저장된 쿠키를 가져옵니다.

Cookies에서 제거하기

cookies.remove(keyName: string, path?: string, domain?: string)

Cookies의 remove() 메서드는 Cookies에서 keyName으로 검색하여 저장된 쿠키를 제거합니다.


1. vue3-cookies 설치하기 (터미널에서 실행합니다.)

npm install vue3-cookies --save

 

2. src\stores\useLoginStore.js 파일을 수정합니다. - Storage에 State를 저장시키는 함수를 추가합니다.

:
import { useCookies } from "vue3-cookies";


export const useLoginStore = defineStore('login', () => {
    :
   const { cookies } = useCookies();
    :

    // Storage에서 state를 저장합니다.
    function saveStateToStorage() {
        const state = {
            access_token : access_token.value,
            refresh_token :  refresh_token.value,
            member_id : member_id.value,
            member_name : member_name.value
        }
        cookies.set("lunchapp.login", JSON.stringify(state));
    }

    :
});

 

3. src\stores\useLoginStore.js 파일을 수정합니다. - 로그인 함수에 추가합니다.

:

export const useLoginStore = defineStore('login', () => {
    :

    // 로그인합니다.
    async function doLogin(memberInfo) {
        :

        try {
            const res = await axios.post("http://127.0.0.1:9000/api/v1/member/login", memberInfo);
            if (res.data.code == 200) {
                :
                // Storage에서 state를 저장합니다.
                saveStateToStorage();
                result = true;
            }
        }
        :
    }

    :

 

4. 웹 브라우저에서 확인하기 (http://localhost:8080)

로그인을 하면 Application의 Cookies에 lunchapp.login이 저장되는 것을 확인할 수 있습니다.

 

Application의 Cookies에 저장된 lunchapp.login의 내용은 다음과 같습니다.

Name : lunchapp.login

Value : %7B%22access_token%22%3A%22eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi7YWM7Iqk7Yq4MSIsInJvbGVzIjoiUk9MRV9VU0VSIiwidHlwIjoiQmVhcmVyIiwic2NvcGUiOiJwcm9maWxlIGx1bmNobWVudS5hbGwiLCJpYXQiOjE3NDQwMTU2NzgsImV4cCI6MTc0NDAxNTczOCwiYXVkIjoibHVuY2htZW51QXBwIiwiaXNzIjoiYXBpc2VydmVyLmV4YW1wbGUuY29tIiwic3ViIjoidGVzdDEiLCJqdGkiOiI5Nzk0NzBkNWE0NmM0MGMyOWM3YWE0NzVhNTg5MDFhMSJ9.v1sxBNVVlKT0rUQzbbmFnvT-lwZApyICxfgMIh_rEjM%22%2C%22refresh_token%22%3A%22eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXAiOiJSZWZyZXNoIiwic2NvcGUiOiJwcm9maWxlIHNjb3BlcyIsImlhdCI6MTc0NDAxNTY3OCwiZXhwIjoxNzQ0MDE1Nzk4LCJhdWQiOiJsdW5jaG1lbnVBcHAiLCJpc3MiOiJhcGlzZXJ2ZXIuZXhhbXBsZS5jb20iLCJzdWIiOiJ0ZXN0MSIsImp0aSI6ImI2ODkxNjEyYzNmOTRlNDNiMzE4YTNjYmZhYmNjZTU1In0.m99wsch3Uz2_Q0BpsPEmLpsN6Qo10XV0IU8BAj25QoA%22%2C%22member_id%22%3A%22test1%22%2C%22member_name%22%3A%22%ED%85%8C%EC%8A%A4%ED%8A%B81%22%7D

Domain : localhost

Path : /

Expires / Max-Age : 2025-04-08T08:47:58.398Z

Size : 876

HTTP :

Secure :

SameSite : Lax

SameParty :

Priority : Medium

 

5. 웹 브라우저를 새로고침(F5)하면 Cookie에는 값은 있지만 로그인이 유지되지 않습니다.

 

6. src\stores\useLoginStore.js 파일을 수정합니다. - Storage에서 State를 읽어오는 함수를 추가합니다.

export const useLoginStore = defineStore('login', () => {
    :

    // Storage에서 state를 읽어옵니다.
    function readStateFromStorage() {
        if (cookies.get("testvue.login") != null) {
            const state = cookies.get("testvue.login");
            if (state.access_token != null) {
                access_token.value = state.access_token;
            }
            if (state.refresh_token != null) {
                refresh_token.value = state.refresh_token;
            }
            if (state.member_id != null) {
                member_id.value = state.member_id;
            }
            if (state.member_name != null) {
                member_name.value = state.member_name;
            }
        }
    }

    return {
        :
        doLogout,
        readStateFromStorage
    }
});

Cookies의 get() 메서드로 JSON 객체를 가져올 때는 자동으로 JSON 객체로 변환됩니다. JSON.parse() 메서드를 사용하지 않아도 됩니다.

 

7. src\App.vue 파일을 수정합니다. - onMounted()를 추가하여 Storage에 있는 State를 읽어오게 합니다.

<script>
import { computed, onMounted } from 'vue';
:

export default {
    name: 'AppView',
    setup() {
        :

        onMounted(()=> {
            loginStore.readStateFromStorage();
        });

        :
    }
}
</script>

 

8. 웹 브라우저를 새로고침(F5)하면 Cookie에 있는 값을 로그인을 유지하는 것을 확인할 수 있습니다.

 

9. src\stores\useLoginStore.js 파일을 수정합니다. - 로그아웃이 되면 쿠키가 삭제되게 합니다.

export const useLoginStore = defineStore('login', () => {
    :

    // 로그아웃합니다.
    async function doLogout() {
        console.log("로그아웃되었습니다.");
        access_token.value = "";
        refresh_token.value = "";
        member_id.value = "";
        member_name.value = "";
        cookies.remove("lunchapp.login");
    }

    :
});

 

10. 로그아웃을 하면 Cookie가 삭제되는 것을 확인할 수 있습니다.

 

 

Store State 유지 - Storage(스토리지) 사용

 

1. 개발자 툴에서 Application를 클릭하고 Local storage를 클릭합니다.

 

Storage 구성

  • Storage는 키(key)와 값(value)으로 구성되어 저장되고 키(key)로 조회됩니다.
  • Storage는 Local Storage(로컬 스토리지)와 Session Storage(섹션 스토리지)가 있습니다.
    • Local Storage(로컬 스토리지)는 영구적으로 저장됩니다. (삭제하지 않는 한 계속 유지됩니다.)
    • Session Storage(섹션 스토리지)는 임시적으로 저장됩니다. (웹 브라우저가 종료되면 사라집니다.)

 

Storage 함수

Local storage에 저장하기

localStorage.setItem(key: string, value: string)

localStorage의 setItem() 메서드는 Local storage에 key로 value를 저장합니다.

 

Local storage에서 가져오기

localStorage.getItem(key: string)

localStorage의 getItem() 메서드는 Local storage에서 key로 검색하여 저장된 값을 가져옵니다.

 

Local storage에서 제거하기

localStorage.removeItem(key: string)

localStorage의 removeItem() 메서드는 Local storage에서 key로 검색하여 저장된 값을 제거합니다.

 

1. src\stores\useLoginStore.js 파일을 수정합니다. - Cookies를 제거하고 localStorage로 변경합니다.

:
//import { useCookies } from "vue3-cookies";


export const useLoginStore = defineStore('login', () => {
    :
   //const { cookies } = useCookies();
    :

    // Storage에서 state를 저장합니다.
    function saveStateToStorage() {
        const state = {
            access_token : access_token.value,
            refresh_token :  refresh_token.value,
            member_id : member_id.value,
            member_name : member_name.value
        }
        //cookies.set("lunchapp.login", JSON.stringify(state));
        localStorage.setItem("lunchapp.login", JSON.stringify(state));
    }

    :
});

 

2. src\stores\useLoginStore.js 파일을 수정합니다. - Cookies를 제거하고 localStorage로 변경합니다.

export const useLoginStore = defineStore('login', () => {
    :

    // Storage에서 state를 읽어옵니다.
    function readStateFromStorage() {
        //if (cookies.get("lunchapp.login") != null) {
        //    const state = cookies.get("lunchapp.login");
        if (localStorage.getItem("lunchapp.login") != null) {
            const state =  JSON.parse(localStorage.getItem("lunchapp.login"));
            if (state.access_token != null) {
                access_token.value = state.access_token;
            }
            if (state.refresh_token != null) {
                refresh_token.value = state.refresh_token;
            }
            if (state.member_id != null) {
                member_id.value = state.member_id;
            }
            if (state.member_name != null) {
                member_name.value = state.member_name;
            }
        }
    }

    :
});

JSON.parse() 메서드를 사용하여 JSON 객체로 변환해야 합니다.

 

3. src\stores\useLoginStore.js 파일을 수정합니다. - Cookies를 제거하고 localStorage로 변경합니다.

export const useLoginStore = defineStore('login', () => {
    :

    // 로그아웃합니다.
    async function doLogout() {
        console.log("로그아웃되었습니다.");
        access_token.value = "";
        refresh_token.value = "";
        member_id.value = "";
        member_name.value = "";
        //cookies.remove("lunchapp.login");
        localStorage.removeItem("lunchapp.login");
    }

    :
});

 

4. 로그인을 하면 Application의 Local storage에 lunchapp.login이 저장되는 것을 확인할 수 있습니다.

 

Application의 Local storage에 저장된 lunchapp.login의 내용은 다음과 같습니다.

Key : lunchapp.login
Value : {"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi7YWM7Iqk7Yq4MSIsInJvbGVzIjoiUk9MRV9VU0VSIiwidHlwIjoiQmVhcmVyIiwic2NvcGUiOiJwcm9maWxlIGx1bmNobWVudS5hbGwiLCJpYXQiOjE3NDQwMzUyMzAsImV4cCI6MTc0NDAzNTI5MCwiYXVkIjoibHVuY2htZW51QXBwIiwiaXNzIjoiYXBpc2VydmVyLmV4YW1wbGUuY29tIiwic3ViIjoidGVzdDEiLCJqdGkiOiIwMWMzNWE1YWVlMmE0YjA1YjM0ZGQ4OWI1MmEwZDZhNiJ9.Smocyex5QHHNigkYh6OTbhqRUK2EtLzKT1oO8Mat3tU","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXAiOiJSZWZyZXNoIiwic2NvcGUiOiJwcm9maWxlIHNjb3BlcyIsImlhdCI6MTc0NDAzNTIzMCwiZXhwIjoxNzQ0MDM1MzUwLCJhdWQiOiJsdW5jaG1lbnVBcHAiLCJpc3MiOiJhcGlzZXJ2ZXIuZXhhbXBsZS5jb20iLCJzdWIiOiJ0ZXN0MSIsImp0aSI6IjdhYWYyMzU0MTIzZDQxNGE5ZTU1YzdjZmQ4MmQyMzQzIn0.P8pc8rT52XZZCzGrB-WUC6pVRt5hMcZ_qD2A6XKkEB0","member_id":"test1","member_name":"테스트1"}

 

5. 로그아웃을 하면 Local storage에 있는 lunchapp.login이 삭제되는 것을 확인할 수 있습니다.

 

 

Pinia Plugin Persistedstate 사용

 

Pinia Plugin Persistedstate (피니아 플러그인 퍼시스트드스테이트)
(https://prazdevs.github.io/pinia-plugin-persistedstate/)

 

1. pinia-plugin-persistedstate 설치하기 (터미널에서 실행합니다.)

npm install pinia-plugin-persistedstate --save

 

2. src\main.js 파일을 수정합니다. - pinia plugin를 추가합니다.

import { createApp } from 'vue';
import { ref } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import router from './router';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
const app = createApp(App);

:

 

3. src\App.vue 파일을 수정합니다. - Storage를 제거합니다.

<script>
import { computed } from 'vue';
:

export default {
    name: 'AppView',
    setup() {
        :

        /*
        onMounted(()=> {
            loginStore.readStateFromStorage();
        });
        */

        :
    }
}
</script>

onMounted를 삭제합니다.

 

4. src\stores\useLoginStore.js 파일을 수정합니다. - Storage를 제거합니다.

:

export const useLoginStore = defineStore('login', () => {
    :

    // 로그인합니다.
    async function doLogin(memberInfo) {
        :

        try {
            const res = await axios.post("http://127.0.0.1:9000/api/v1/member/login", memberInfo);
            if (res.data.code == 200) {
                :
                // Storage에서 state를 저장합니다.
                //saveStateToStorage();
                result = true;
            }
        }
        :
    }
    :

    // 로그아웃합니다.
    async function doLogout() {
        console.log("로그아웃되었습니다.");
        access_token.value = "";
        refresh_token.value = "";
        member_id.value = "";
        member_name.value = "";
        //localStorage.removeItem("lunchapp.login");
    }

    /*
    // Storage에서 state를 저장합니다.
    function saveStateToStorage() {
        const state = {
            access_token : access_token.value,
            refresh_token :  refresh_token.value,
            member_id : member_id.value,
            member_name : member_name.value
        }
        localStorage.setItem("lunchapp.login", JSON.stringify(state));
    }

    // Storage에서 state를 읽어옵니다.
    function readStateFromStorage() {
        if (localStorage.getItem("lunchapp.login") != null) {
            const state =  JSON.parse(localStorage.getItem("lunchapp.login"));
            if (state.access_token != null) {
                access_token.value = state.access_token;
            }
            if (state.refresh_token != null) {
                refresh_token.value = state.refresh_token;
            }
            if (state.member_id != null) {
                member_id.value = state.member_id;
            }

            if (state.member_name != null) {
                member_name.value = state.member_name;
            }
        }
    }
    */

    return {
        access_token,
        refresh_token,
        member_id,
        member_name,
        isLogin,
        doLogin,
        isAccessTokenExpire,
        doRefreshToken,
        doLogout
        //readStateFromStorage
    }
});

 

5. src\stores\useLoginStore.js 파일을 수정합니다. - 3번째 파라미터에 { persist:true }를 추가합니다.

import { defineStore } from 'pinia';
import { ref } from 'vue';
import axios from 'axios';

export const useLoginStore = defineStore('login', () => {
    :
}, { persist:true });

 

6. 로그인을 하면 Application의 Local storage에 login으로 저장되는 것을 확인할 수 있습니다.

 

Application의 Local storage에 저장된 login의 내용은 다음과 같습니다.

Key : login
Value : {"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi7YWM7Iqk7Yq4MSIsInJvbGVzIjoiUk9MRV9VU0VSIiwidHlwIjoiQmVhcmVyIiwic2NvcGUiOiJwcm9maWxlIGx1bmNobWVudS5hbGwiLCJpYXQiOjE3NDQwMzUyMzAsImV4cCI6MTc0NDAzNTI5MCwiYXVkIjoibHVuY2htZW51QXBwIiwiaXNzIjoiYXBpc2VydmVyLmV4YW1wbGUuY29tIiwic3ViIjoidGVzdDEiLCJqdGkiOiIwMWMzNWE1YWVlMmE0YjA1YjM0ZGQ4OWI1MmEwZDZhNiJ9.Smocyex5QHHNigkYh6OTbhqRUK2EtLzKT1oO8Mat3tU","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXAiOiJSZWZyZXNoIiwic2NvcGUiOiJwcm9maWxlIHNjb3BlcyIsImlhdCI6MTc0NDAzNTIzMCwiZXhwIjoxNzQ0MDM1MzUwLCJhdWQiOiJsdW5jaG1lbnVBcHAiLCJpc3MiOiJhcGlzZXJ2ZXIuZXhhbXBsZS5jb20iLCJzdWIiOiJ0ZXN0MSIsImp0aSI6IjdhYWYyMzU0MTIzZDQxNGE5ZTU1YzdjZmQ4MmQyMzQzIn0.P8pc8rT52XZZCzGrB-WUC6pVRt5hMcZ_qD2A6XKkEB0","member_id":"test1","member_name":"테스트1"}

 

Key는 Store의 ID입니다.

Value는 Store의 State의 값으로 JSON 객체를 만들어 저장합니다.

export const useLoginStore = defineStore('login', () => {
    const access_token = ref("");
    const refresh_token = ref("");
    const member_id = ref("");
    const member_name = ref("");
    :
}, { persist:true });

 

7. 로그아웃을 하면 Local storage에 있는 login에 저장된 JSON 객체의 값이 제거되는 것을 확인할 수 있습니다.

 

 

Pinia Plugin Persistedstate 구성

  • key : Storage의 key를 설정합니다.  (기본값: store.$id)
  • storage : 저장소(localStorage, sessionStorage)를 설정합니다. (기본값: localStorage)
  • serializer : 저장하기전에 직렬화하고 가져올 때 역직렬화를 설정합니다. (기본값: JSON.stringify/destr)
  • pick : 선택적으로 저장할 state를 선택합니다. (기본값: 없음, 전체 state가 저장됩니다.)
  • omit : 선택적으로 생략할 state를 선택합니다. (기본값: 없음)
  • beforeHydrate : Storage에서 가져오기 전에 호출되는 Callback 함수를 설정합니다. (기본값: 없음)
  • afterHydrate : Storage에서 가져온 후에 호출되는 Callback 함수를 설정합니다. (기본값: 없음)

 

1. src\stores\useLoginStore.js 파일을 수정합니다. - 3번째 파라미터에 persist를 구성하여 추가합니다.

import { defineStore } from 'pinia';
import { ref } from 'vue';
import axios from 'axios';

export const useLoginStore = defineStore('login', () => {
    :
}, { persist: {
        key: 'lunchapp.login',
        storage: localStorage,
        pick: ['access_token'],
        beforeHydrate:(ctx => console.log(`about to hydrate ${ctx.store.$id}`)),
        afterHydrate:(ctx => console.log(`just hydrated ${ctx.store.$id}`))
    }
});

localStorage에 lunchapp.login이란 Key로 access_token만 저장합니다.

Storage에서 가져오기 전과 후에 콘솔로 로그를 출력합니다.

 

2. 로그인을 하면 Application의 Local storage에 lunchapp.login으로 저장되는 것을 확인할 수 있습니다.

 

3. Application의 Local storage에 저장된 lunchapp.login의 내용은 다음과 같습니다.

Key : lunchapp.login
Value : {"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi7YWM7Iqk7Yq4MSIsInJvbGVzIjoiUk9MRV9VU0VSIiwidHlwIjoiQmVhcmVyIiwic2NvcGUiOiJwcm9maWxlIGx1bmNobWVudS5hbGwiLCJpYXQiOjE3NDQwNDEyMzIsImV4cCI6MTc0NDA0MTI5MiwiYXVkIjoibHVuY2htZW51QXBwIiwiaXNzIjoiYXBpc2VydmVyLmV4YW1wbGUuY29tIiwic3ViIjoidGVzdDEiLCJqdGkiOiJlMWYyM2U1YjcyMTA0NmM0OWNiYWM2YzMyMDExM2EzNSJ9.JLQ_MdFngg0-0dN_Ncx885ELzmqPba0EZUiXSOWG06M"}

테스트로 access_token만 저장되기 때문에 로그인이 유지되지 않습니다.

728x90
반응형