[NextJS] 채널톡 Script 추가하기

2022. 4. 28. 09:18API,SDK

728x90

얼마전에 Next JS로 진행하고 있는 프로젝트에 채널톡을 추가하게 되었는데 스크립트 추가하는 방법에 대한 글이 없어서 필자가 채널톡을 추가할때 사용한 방법을 해당 포스트에서 공유해드리고자 합니다. 


채널톡 스크립트 추가하기

채널톡은 플러그인 스크립트를 통해 추가 해줄 수가 있는데 현재 개발자 문서에서 안내하는 두가지 방법으로는 정적페이지일 경우 index.html에 script를 즉시실행함수 형식으로 추가해주는 방식과 SPA(Single Page Application)일 경우 클래스로 관리를 해서 DOM이 생성된 경우 스크립트를 추가하는 방법이 있습니다.

 

Next JS는 정확하게 구분하면 정적 페이지도 아니고 SPA도 아니지만 조금 변형해서 두가지 방법 모두 사용 가능합니다.

 


dangerouslySetInnerHTML

 

첫번째 방법을 사용하고 싶다면 _document.js (head나 body 커스터마이징을 위해 필요한 파일)에 개발자 문서에서 가져온 스크립트 예제를 dangerouslySetInnerHTML로 넣어주시면 됩니다. 해당 방법에 대해서는 이미 어느 개발자분께서 작성한글이 있기 때문에 해당 포스트를 참고해주길 바랍니다.

 

만약 모든 페이지가 아닌 특정 페이지에서만 보이게 하고 싶다면 해당 페이지에 Script 컴포넌트를 사용해 삽입할수 있습니다. 

 

import Script from "next/script";

 

Next에서 자체 지원하는 Script 컴포넌트는 v.11.0.0에 추가된 컴포넌트로 개발자들이 최적의 방법으로 스크립트를 추가할수 있도록 생겨났다고 합니다.

 

공식문서에 따르면,

  • 채팅 관련 플러그인 (Chat support plugins)
  • 소셜 미디어 위젯 (Social media widgets)

바로 추가 되지 않아도 앱 사용에 큰 지장이 없는 low priority의 스크립트에 한해서 lazyOnLoad를 사용하기를 권장하고 있기에 아래처럼 스크립트를 작성해 페이지에 추가해주시면 됩니다.

 

플러그인 키는 채널톡 로그인후 설정 -> 일반설정 -> 버튼 설치 및 설정에서 확인하실수 있습니다.

 

 <Script
    id="channelTalk"
    strategy="lazyOnload"
    dangerouslySetInnerHTML={{
      __html: `(function() {
    var w = window;
    if (w.ChannelIO) {
      return (window.console.error || window.console.log || function(){})('ChannelIO script included twice.');
    }
    var ch = function() {
      ch.c(arguments);
    };
    ch.q = [];
    ch.c = function(args) {
      ch.q.push(args);
    };
    w.ChannelIO = ch;
    function l() {
      if (w.ChannelIOInitialized) {
        return;
      }
      w.ChannelIOInitialized = true;
      var s = document.createElement('script');
      s.type = 'text/javascript';
      s.async = true;
      s.src = 'https://cdn.channel.io/plugin/ch-plugin-web.js';
      s.charset = 'UTF-8';
      var x = document.getElementsByTagName('script')[0];
      x.parentNode.insertBefore(s, x);
    }
    if (document.readyState === 'complete') {
      l();
    } else if (window.attachEvent) {
      window.attachEvent('onload', l);
    } else {
      window.addEventListener('DOMContentLoaded', l, false);
      window.addEventListener('load', l, false);
    }
  })();
  ChannelIO('boot', {
    "pluginKey": "${process.env.NEXT_PUBLIC_CHANNEL_IO_KEY}"
  });
	`,
    }}
  />;

 


useEffect 

dangerouslySetInnerHTML을 사용하기 꺼려진다면 필자가 추천하는건 두번째 방식으로 클래스로 관리를 하고 useEffect훅이 실행 될때 (window 전역 객체에 접근 가능할때) 스크립트를 추가시켜주는 방법입니다. 필자가 해당 방법을 채택하게 된건 해당 방식을 사용하면 관리가 편하고 회원정보 연동을 좀 더 간편하게 할 수 있어서 입니다.

 

일단 클래스는 직접 구현을 할 필요없이 개발자 문서에 예제가 있기 때문에 해당 예제를 사용하도록 합시다.

개선의 여지는 있지만 문법이나 변수는 따로 수정하지 않겠습니다.

 

// ChannelService.js
// https://developers.channel.io/docs/web-installation

class ChannelService {
  constructor() {
    this.loadScript();
  }

  loadScript() {
    var w = window;
    if (w.ChannelIO) {
      return;
    }
    var ch = function() {
      ch.c(arguments);
    };
    ch.q = [];
    ch.c = function(args) {
      ch.q.push(args);
    };
    w.ChannelIO = ch;
    function l() {
      if (w.ChannelIOInitialized) {
        return;
      }
      w.ChannelIOInitialized = true;
      var s = document.createElement('script');
      s.type = 'text/javascript';
      s.async = true;
      s.src = 'https://cdn.channel.io/plugin/ch-plugin-web.js';
      s.charset = 'UTF-8';
      var x = document.getElementsByTagName('script')[0];
      x.parentNode.insertBefore(s, x);
    }
    if (document.readyState === 'complete') {
      l();
    } else if (window.attachEvent) {
      window.attachEvent('onload', l);
    } else {
      window.addEventListener('DOMContentLoaded', l, false);
      window.addEventListener('load', l, false);
    }
  }

  boot(settings) {
    window.ChannelIO('boot', settings);
  }

  shutdown() {
    window.ChannelIO('shutdown');
  }
}

export default new ChannelService();

 

위와 같이 ChannelService라는 클래스를 따로 관리를 해주시면 되는데 해당 예제를 바로 사용하면 당연히 에러가 날겁니다.

 

이유는 해당 예제에서는 클래스를 내보내지않고 클래스(생성자 함수)로 생성한 인스턴스를 내보내기 때문입니다.

ChannelService의 인스턴스는 생성시 loadScript 함수가 호출되는데 이 시점에 window는 undefined이기 때문에 이후에 스크립트를 붙혀주는 코드가 정상적으로 작동할수 없습니다.

 

해결방법으로는 생성자 함수 호출이 useEffect 훅 내부 (window가 undefined가 아닌 시점에) 에서 일어나도록 하기만 하면 됩니다. 그러기 위해서는 호출을 해서 내보내지 않고 원하는곳에서 인스턴스를 생성하여 사용할수 있도록 생성자함수 자체를 내보내도록 합시다.

 

export default ChannelService;

 

이제 사용하고자 하는 페이지의 useEffect 훅 내부에서 생성자 함수로 인스턴스를 만들고 플러그인 키와 사용자 정보(회원정보를 연동해야할 경우)를 boot 메소드에 넘겨주면 됩니다.

 

몇개의 회원정보 (이름 , 휴대폰 번호, 이메일 등) 을 넘겨주게 되면 상담 신청을 할때 기본정보들이 다 채워져서 나오기 때문에 고객 입장에서 매우 편리합니다. 로그인 시스템이 있는 사이트를 운영하신다면 아래와 같은 방식으로 채널톡 설정을 마무리 해주시면 됩니다.

 

// Home.js

import ChannelService from "components/ChannelService";

const Home = () => {
    // 생략 ... 로그인한 유저 정보 불러오기 (필자는 recoil 사용)
    useEffect(() => {
        const channelTalk = new ChannelService();
        const {isLoggedIn, uid, name, mobileNumber, email, avatarUrl} = user;
        if (isLoggedIn) {
          channelTalk.boot({
            pluginKey: process.env.NEXT_PUBLIC_CHANNEL_IO_KEY,
            memberId: uid,
            profile: {
              name,
              mobileNumber,
              email,
              avatarUrl,
            },
          });
        } else {
          channelTalk.boot({
            pluginKey: process.env.NEXT_PUBLIC_CHANNEL_IO_KEY,
          });
        }
        return () => {
          channelTalk.shutdown();
        };
      }, [user]);
      
      // 생략
  }

 

플러그인키와 함께 회원정보 전달시 회원 정보가 미리 입력되어 나옵니다.


참고자료:

 

https://nextjs.org/docs/basic-features/script

https://developers.channel.io/docs/web-installation

https://medium.com/nextjs/the-script-component-in-next-js-ee6ee6cd705a