API Cache를 사용해 SPA 페이지 사용자 경험 개선하기(w. axios, react)

API Cache를 사용해 SPA 페이지 사용자 경험 개선하기(w. axios, react)

API Cache를 사용해 SPA 페이지 사용자 경험 개선하기 (w. axios, react)

대부분의 SPA(Single Page Application)는 화면을 그리는 로직들이 모두 클라이언트에 존재하고, 화면을 그리는데 필요한 데이터만 서버에서 요청하여 가져오는 방식으로 구현하고 있습니다. 이렇게 할 경우 MPA(Multiple Page Application)보다 자연스러운 사용자 경험(UX)나 필요한 리소스만 부분적으로 로딩하기에 성능적으로 유리한 장점이 있습니다. 그러나 SPA에서도 “뒤로가기”, “앞으로가기” 등의 동작을 수행 할 경우 이미 한 번 불러왔던 데이터를 다시 불러오는 불필요한 로딩이 발생하게 되는데, 이 때 API Cache를 사용하면 불필요한 API 통신을 막아 사용자에게 더 좋은 사용자 경험을 제공할 수 있습니다.

Axios Extensions

평소에 비동기 통신을 Axios 라이브러리를 사용하여 구현하고 있는데, Axios에서는 어댑터(Adapter)를 통해 요청 핸들링을 커스텀 할 수 있는 방법을 제공하고 있습니다. Axios Extensions에서 제공하는 cacheAdapterEnhancer를 활용하면 API 요청을 캐싱하기 위한 어댑터를 매우 쉽게 생성 할 수 있습니다.

cacheAdapterEnhancer 사용법

생성

import axios from 'axios';
import { cacheAdaperEnhancer } from 'axios-extensions';

const instance = axios.create({
  baseUR: '/',
  Accept: 'application/json',
  headers: { 'Cache-Control': 'no-cache' }
  adapter: cacheAdapterEnhancer(axios.defaults.adapter),
})
Cache-Control Header

Cache-Control 헤더는 서버와 브라우저 사이의 캐시 정책을 명시합니다. 이 헤더값에 따라 브라우저가 해당 파일을 캐시해야하는지 언제 다시 서버에 요청하는지 결정하게 됩니다.

별다른 설정을 하지 않을 경우 cache will be enabled 되며, 커스텀한 캐싱을 원할 경우 no-cache로 설정을 해줘야 합니다. no-cache는 브라우저가 서버의 응답을 캐시할 지 개발자 스스로 결정 할 수 있습니다.

cacheAdapterEnhancer

cacheAdapterEnhancer에 axios의 기본 adapter를 넘겨줍니다. 내부로직을 확인해보면 buildSortedURL를 사용하여 요청들을 모두 index화 하여 caching합니다. 매 호출 시 마다 cache.get를 통해 기존에 호출 했던 적이 있다면 기존의 data를 내려주는 방식으로 구현되어 있습니다.

사용

instance.get('/users'); // 1
instance.get('/users'); // 2
instance.get('/users', { cache: false }); // 3
1. 첫번째 GET /users 요청

users에 대한 요청이 처음 발생했기 때문에 실제 네트워크 요청이 발생한다.

2. 두번째 GET /users 요청

이미 users에 대한 요청이 1에서 캐싱 되었으므로 실제 요청을 하지 않고 1에 캐싱된 데이터를 반환한다.

3. 세번째 GET /users 요청

기본적으로 cacheAdapterEnhancetr의 기본 설정은 모든 요청을 캐싱한다. 다만 처음 캐싱이 일어난 후 새로운 데이터를 가져오고 싶다면, cache: false 옵션을 전달하면 된다. 해당 옵션을 포함한 요청은 캐싱 여부와 상관없이 실제 네트워크 요청이 발생 된다.

활용

위와 같은 방식으로 캐시 어댑터를 적용하게 되면 모든 네트워크 요청이 캐싱되므로, 사용자가 데이터를 갱신하고 싶은 상황에 문제가 발생한다. 따라서 뒤로가기 또는 앞으로 가기 동작 수행 시에만 캐싱된 데이터를 사용하도록 하고 새로운 데이터를 필요로 하는 상황에서는 캐싱된 데이터가 아닌 새로운 데이터를 받아오는 것이 가장 좋은 방법이라 생각된다.

  • axios instance 생성 및 fetch method

    import axios from 'axios';
    import { cacheAdaperEnhancer } from 'axios-extensions';
    
    const instance = axios.create({
    baseUR: '/',
    Accept: 'application/json',
    headers: { 'Cache-Control': 'no-cache' }
    adapter: cacheAdapterEnhancer(
    	axios.defaults.adapter,
      { enabledByDefault: false }
    ),
    });
    
    export const getUser = async (forceUpdate: boolean = false) => {
    const response = await instance.get(
      '/',
      {
        forceUpdate,
        cache: true,
      },
    );
    
    return response.data;
    };

    먼저 enabledByDefault 옵션을 false로 설정하여 모든 네트워크 요청에 대해 캐싱된 데이터를 사용하지 않도록 합니다. 만약 새로운 데이터를 필요로 한다면 forceUpdatetrue 값을 넘겨 줍니다.forceUpdate는 데이터를 통신하여 최신화 합니다.

  • React

    import React, { useEffect, useState } from 'react';
    import getUser from '@/apis/user';
    
    const User = ({ history }) => {
    const [userInfo, setUserInfo] = useState(null);
    const fetchUser = async (forceUpdate) => {
      const data = await getUser(forceUpdate);
      setUserInfo(data);
    }
    
    useEffect(() => {
      fetchUser(history.action === 'PUSH');
    }, [history.action]);
    
    return (
      <article>
        {
          userInfo && (
          	<figure>
               <img src={userInfo.image} alt="유저 이미지"/>
              <figcaption>{userInfo.name}</figcaption>
            </figure>
          )
        }
        <button onClick={() => { fetchUser(true); }}>새로고침</button>
      </article>
    )
    };

    기본적으로 새로고침 버튼을 통해 업데이트 할 경우에는 forceUpdate를 강제한다.

    그 외에는 history.action을 통해 사용자가 링크 클릭을 한 것인지, 뒤로가기 또는 앞으로가기 동작을 수행한 것인지 판단하여 캐싱된 데이터를 사용하도록 할 것인지 여부를 알 수 있다. (링크를 클릭하는 경우 history.actionPUSH이고, 뒤로가기 또는 앞으로가기 동작 수행시엔 POP이 발생한다.)

    🧐 위 코드는 좀 더 간소화 할 수 있을 듯 하다..

만약 axios 0.19.0 버전을 사용하고 있다면?

현재 제일 최신 버전은 0.19.2 이기에 새롭게 프로젝트 세팅을 하는 분들은 큰 문제가 없으나, 작년까지는 0.19.0이 최신 버전이었고 커스텀 설정 옵션과 관련된 버그가 있어 forceUpdate 옵션을 사용할 수 없습니다. 해당 옵션을 사용하시려면 0.19.1 이상의 버전을 사용하시거나, 0.18.1 버전을 사용해야 합니다.

😢 동작이 안되어서 한참 헤맴..ㅠ

위의 과정처럼 axios 내 cacheAdapterEnhancer 를 사용한다면 React 환경의 SPA에서도 매우 간단하게 사용자 경험을 개선 할 수 있습니다. (통신의 비효율을 줄여주기에 성능에도 이득!)

참고자료

🌝동글동글 🌚