본문 바로가기

우아한테크코스/행동대장

행동대장 성능 : React SPA 환경에서 Kakao Script 필요할 때 다운로드 받기

이번에도 이어지는 성능 이슈!

 

저번에 화면에 보이는 이미지만 로드해서 초기 로딩 속도를 개선했다면 이번에는 index.html에 있는 외부 스크립트를 필요할 때 불러오는 성능 개선을 했다.

 

랜딩 페이지에서는 필요하지 않은 카카오 스크립트

행동대장에서는 카카오톡 링크 공유를 위해서 Kakao api를 이용하고 있다. 하지만 이는 이벤트 페이지에 접속해야 필요하지 랜딩 페이지에서는 전혀 필요하지 않다. 그래서 랜딩 페이지에서는 카카오를 불러오지 않고 필요한 이벤트 페이지에서 불러오게 한다면 랜딩 페이지 초기 로딩 속도를 더 개선할 수 있을 것 같아 진행해봤다.

 

현재 성능

저번 이미지 최적화 이후의 결과와 동일하다.

총 14번의 request가 발생했고 761KB의 데이터 전송량, 1.3MB의 리소스 사용량, 모든 요소가 로드된 Finish time은 2.43초, DomContentLoaded는 331ms, Load는 332ms가 걸렸다.

 

여기서 주목할 것은 Kakao.min.js이다.

지금은 랜딩 페이지에서도 카카오 스크립트를 불러오고 있지만 필요하지 않은 카카오 스크립트를 불러오지 않는다면 27KB를 절약할 수 있을 것이다.

 

아래는 Performance 탭에서 성능을 측정한 결과이다. 카카오 스크립트 관련 결과를 보게되면 21.19ms동안 Render blocking 현상이 일어났다. 이도 역시 개선할 수 있을 듯하다.

 

카카오 공식문서를 보면

Kakao SDK 가이드를 보게되면 아래 스크립트를 html에 넣고 Kakao.init()을 호출하라는 이야기밖에 없다.

<script src=""></script>를 넣게 되는 순간 외부 스크립트를 다운로드 받을 수밖에 없게 된다.

카카오 공식문서를 보게되면

 

React는 SPA 기반 라이브러리이기 때문에 어느 페이지에 처음 접속해도 index.html을 먼저 불러오게 되며 index.html을 실행하게 되면 저 script를 다운로드 받게 된다. 그렇다면 어떻게 하면 SPA 환경에서 카카오 스크립트를 필요할 때 불러올 수 있을까?

 

해답은 동적으로 스크립트를 추가해서 넣으면 된다.

이 아이디어는 프론트엔드 크루 리안의 글에서 얻을 수 있었다. (리안 감사합니다~)

 

카카오 스크립트를 동적으로 다운로드 받는 방법

먼저 index.html에서 아래 스크립트를 지워주어야한다.

 <script
   src="https://t1.kakaocdn.net/kakao_js_sdk/{version}/kakao.min.js"
   integrity="{integrity_value}"
   crossorigin="anonymous">
 </script>

 

그리고 위 태그를 동적으로 만들기 위해 kakaoScript라는 객체를 만들었다.

여기서 주목할 것이 loaded 프로퍼티인데 이는 아래에서 설명할 예정

const kakaoScript = {
  url: 'https://t1.kakaocdn.net/kakao_js_sdk/{version}/kakao.min.js',
  integrity: 'integrity_value',
  crossOrigin: 'anonymous',
  loaded: false,
};

 

createElement를 사용해 script 태그를 하나 만든 뒤 위 객체의 프로퍼티들을 하나하나 스크립트에 매핑시켜준 뒤에 document의 head 태그 마지막에 appendChild를 이용해 script를 붙여주면 된다. 이 함수가 실행되면 동적으로 index.html에 카카오 스크립트가 붙게되고 우리가 원할 때 카카오 스크립트를 불러올 수 있게 된다.

const script = document.createElement('script');
script.src = kakaoScript.url;
script.integrity = kakaoScript.integrity;
script.crossOrigin = kakaoScript.crossOrigin;
script.async = true;

document.head.appendChild(script);

 

 

 

여기서 loaded를 추가해 준 이유는 이 함수가 다시 실행된다면 또 다시 스크립트를 불러올 것이기 때문이다. 그래서 이미 스크립트를 불러왔다면 다시 불러오지 않게 하도록 방어막을 친 것이다.

 

함수 상단에 kakaoScript 객체의 loaded 프로퍼티가 true라면 이미 스크립트를 불러온 것이니 early return을 시켜줬다.

그리고 script가 onload 될 때 loaded 프로퍼티를 true로 만들어줘서 카카오 스크립트가 불러와졌다는 표시를 해줬다.

if (kakaoScript.loaded) return;

...

script.onload = () => {
  kakaoScript.loaded = true;
};

 

이렇게 카카오 스크립트를 동적으로 불러와서 Kakao.init 메서드를 실행하면 카카오 서비스가 필요한 곳에서 동적으로 카카오 스크립트를 받아와 init을 할 수 있게 된 것이다. 

loadKakaoScript();

if (window.Kakao && !window.Kakao.isInitialized()) {
  window.Kakao.init(process.env.KAKAO_JAVASCRIPT_KEY);
}

 

하지만 loadKakaoScript가 되고 카카오 스크립트가 모두 다운로드가 되고 나서야 window.Kakao를 사용할 수 있는데 스크립트를 다운로드 받는 도중에 window.Kakao에 접근하게 되면 에러가 날 수 밖에 없다.

 

자 그러면 loadKakaoScript가 되는 것을 기다리고 아래 문장이 실행이 되어야한다면 어떤 조치를 취하면될까? 프로미스를 사용하면 된다. 프로미스를 사용해서 script가 onload 됐을 때 resolve 시켜준다면 스크립트가 불러와졌음을 보장받을 수 있으므로 window.Kakao에 접근할 수 있게 된다.

 

아래는 프로미스까지 적용한 전체 코드이다.

const kakaoScript = {
  url: 'https://t1.kakaocdn.net/kakao_js_sdk/{version}/kakao.min.js',
  integrity: 'integrity_value',
  crossOrigin: 'anonymous',
  loaded: false,
};

const loadKakaoScript = () => {
  return new Promise((resolve, reject) => {
    if (kakaoScript.loaded) {
      resolve(null);
      return;
    }

    const script = document.createElement('script');
    script.src = kakaoScript.url;
    script.integrity = kakaoScript.integrity;
    script.crossOrigin = kakaoScript.crossOrigin;
    script.async = true;

    script.onload = () => {
      kakaoScript.loaded = true;
      resolve(null);
    };

    script.onerror = error => {
      reject(error);
    };

    document.head.appendChild(script);
  });
};

const initKakao = async () => {
  try {
    await loadKakaoScript();

    if (window.Kakao && !window.Kakao.isInitialized()) {
      window.Kakao.init(process.env.KAKAO_JAVASCRIPT_KEY);
    }
  } catch (error) {
    console.error('Kakao SDK 로드 중 오류 발생:', error);
  }
};

export default initKakao;

 

이렇게 해서 카카오 스크립트를 동적으로 필요한 곳에서 불러올 수 있게 됐으며 이 함수를 중복적으로 실행한다고 해서 여러번 스크립트를 불러오지 않게 되는 기능을 만들 수 있다.

 

카카오 스크립트가 동적으로 잘 불러와질까?

사용처에서 버튼 이벤트 핸들러에 initKakao를 추가해줬고 카카오 스크립트는 정산 초대하기 버튼을 누를 때 불러오게 해줬다.

<Dropdown base="button" baseButtonText="정산 초대하기" onBaseButtonClick={initKakao}>
  <DropdownButton text="링크 복사하기" onClick={copyAndToast} />
  <DropdownButton text="카카오톡으로 초대하기" onClick={kakaoShare} />
</Dropdown>

 

 

 

의도한대로 정산 초대하기 버튼을 눌렀을 때 카카오 스크립트가 불러와지는 모습!

자 그렇다면 필요한 곳에서 카카오 스크립트를 불러오게 했으니 랜딩 페이지에서의 개선된 성능을 확인해 볼 시간

 

랜딩 페이지 개선된 성능

 

 

총 13번의 request가 발생했고 731KB의 데이터 전송량, 1.3MB의 리소스 사용량, 모든 요소가 로드된 Finish time은 1.90초, DomContentLoaded는 235ms, Load는 236ms가 걸렸다.

 

개선한 결과 데이터 전송량은 761KB에서 731KB로 30KB 감소, 리소스는 1.3MB에서 1.3MB로 동일, Finish time은 2.43에서 1.90s로 0.5s 감소, DomContentLoaded은 331ms에서 235ms로 100ms 정도 감소, Loading은 332ms 에서 236ms로 100ms 정도 감소한 결과를 얻을 수 있었다.

 

이렇게 랜딩 페이지에서 필요하지 않은 요청들을 계속 분리해간다면 언젠가 행동대장의 LightHouse 점수도 초록불이 뜰 수 있지 않을까?

행동대장의 성능 점수.. 아직 갈 길이 멀다..