[GTM] 클릭한 id를 추적하지 못하는 이슈

Jongco·2023년 10월 8일
3

Intro

이번 프로젝트를 진행하면서, 프로젝트에 심어놓은 GTM이 클릭한 id를 추적하지 못하는 이슈가 등장했다.
특히 이상했던 점은 어떤 것은 추적하고, 어떤 것은 추적하지 못하였다.
심지어 같은 버튼을 클릭해도 잡힐 때와, 안 잡힐 때가 있었다.
인터넷이 불안정해서 못잡는 건가..? 고민도 해보았으나, 그럴 리가 없지.

  • 빨간색: 안 잡히는 부분
  • 초록색: 잡히는 부분

GTM(Google Tag Manager)

이를 해결하기 위해 GTM을 살펴보았다.
GTM에 관련해서는 GTM, Google Tag Manager 뜯어보기 글에 설명이 너무 잘 되어 있다.

해당 글에서 가장 주의깊게 보았던 부분은 아래와 같다.

GTM은 dataLayer라는 자바스크립트 전역 변수를 감시하고 있으며, 이 변수에 데이터가 들어오는 것을 감지하여 트리거를 발동하거나 변수에 값을 할당하고 있다.

모든 이벤트는 dataLayer에 저장되고, dataLayer를 통해 트리거가 발동된다.
이를 확인하기 위해서는 GTM을 심은 프로젝트에서 아무 데나 클릭 후,
콘솔창에 dataLayer만 입력하면 수집한 이벤트를 확인할 수 있다.

추가된 이벤트

이벤트를 수집하지 못하는 이유

이제 클릭한 후에 dataLayer를 살펴보자.
아래 사진에서 빨간색이 글자를 클릭했을 때, 초록색이 글자 옆 공간을 클릭했을 때이다.

그리고 해당 Button 컴포넌트는 아래와 같이 구성되어 있다.

//Button.tsx

const Button = ({
  children, //
  width = '60px',
  height = '36px',
  onClick,
  leftIcon,
  color = 'primary',
  id,
  loading = false,
}: ButtonProps) => {
  return (
    <Style.Button width={width} height={height} color={color} onClick={onClick} disabled={color.includes('disabled') || loading} id={id}>
      <Style.InnerText>
        {loading ? (
          <Style.LoadingCircle color={color}>
            <circle cx="50%" cy="50%" r="10"></circle>
          </Style.LoadingCircle>
        ) : (
          <>
            {leftIcon && <Style.Icon>{leftIcon}</Style.Icon>}
            {children}
          </>
        )}
      </Style.InnerText>
    </Style.Button>
  );
};

자세히 살펴보면 Button 컴포넌트 안에 InnerText란 태그로 감싸져 있고,
GTM은 클릭한 대상을 dataLayer에 등록하기 때문에
위 사진에서 span태그인 InnerText 를 추적하는 것으로 유추해 볼 수 있었다.

이제 이유를 알았으니, 해결해 보도록 하자.

해결방안

첫번째로 InnerText를 없애보려 했다.
하지만 이 방식은 Button 컴포넌트에서만 해결할 수 있는 방식이었고,
만약 children이 태그가 감싸진 형태로 넘어오게 되면 똑같은 현상이 발생한다.
동일하게 아이콘을 클릭해도 이벤트는 잡히지 않을 것이다.

  • 감싸고 있는 InnerText컴포넌트를 제거 ❌

두번째로, 내부의 모든 컴포넌트에 id를 등록하는 것이다.
생각만 해도 벌써 귀찮다..
만약에 추적해야 하는 id 아래에 태그들이 열 개 있으면 열 개를 전부 등록해 주어야 한다..
그리고 코드 또한 id 범벅이 될 것이다.

<button id="create">
  <div id="create">
    <div id="create">
		...	
        ...
        ...
    </div>
  </div>
</button>;
  • 감싸고 있는 InnerText컴포넌트를 제거 ❌
  • 내부의 모든 컴포넌트에 id를 등록 ❌

마지막인 click을 했을 때 dataLayer에 직접 등록을 해주는 방식을 선택했다.
직접 등록해 주는 것은 단순하다.
클릭했을 때 실제로 GTM이 클릭 이벤트를 수집하는 방식을 흉내내어 넣어주면 될 것 같다.

//dataLayer에 직접 등록하는 함수
dataLayer.push({ event: 'gtm.click', 'gtm.elementId': '등록할 아이디' })
  • 감싸고 있는 InnerText컴포넌트를 제거 ❌
  • 내부의 모든 컴포넌트에 id를 등록 ❌
  • 클릭 시, dataLayer에 직접 등록 🟢

그런데 어떻게..?

이벤트 버블링

모든 onClick 이벤트에 dataLayerpush할 수는 없다.
코드도 섞여서 별로 좋지 않고, 사실 귀찮은 게 제일 크다..
한 번에 한 곳에서 이 문제를 해결하고 싶었다.

그래서 우리 팀은 이벤트 버블링을 통해 해당 문제를 해결하고자 했다.
모든 onClickdataLayer.push() 함수를 넣어두기 보다는
어차피 이벤트 버블링이 발생하기 때문에,
가장 최상위 태그에 함수를 넣어두면 어디에서 클릭하든지 해당 함수가 발동될 것이다.

이제는 id만 찾으면 된다.

부모 Element 탐색

HTMLElement에는 parentElement란 프로퍼티가 존재한다.
즉, 부모에 관한 정보는 parentElement로 찾을 수 있다.
하지만 감싸고 있는 부모가 한겹이라는 보장이 없기에,
재귀함수를 통해 가장 최상위 Elementapp까지 탐색을 진행하고,
그 전까지만 id를 탐색하도록 설정하였다.

//부모의 id를 탐색하는 함수

  const findParentId = (target: HTMLElement, flag = true) => {
      if (target?.id === 'app') {
        //아이디가 아무 것도 없음
        return;
      }

      if (target?.id !== '' && flag) {
        //타겟에 아이디가 존재한다
        return;
      }

      if (target?.id !== '') {
        //부모에 아이디가 존재한다
        dataLayer.push({ event: 'gtm.click', 'gtm.elementId': target?.id as string });
        return;
      }

      findParentId(target.parentElement!, false);
  };

아래와 같이 이제는 추적하고자 하는 id를 추적할 수 있었다.

아쉬운 점

물론 id 를 추적하는 데에는 성공 했지만, 아쉬운 점이 한 둘이 아니다.
가장 크리티컬한 이슈는 원하는 버튼의 클릭 횟수를 정확하게 파악할 수는 있지만,
해당 로직과는 별도로 GTM에서 클릭 이벤트를 수집하고 있기 때문에
전체 클릭 횟수는 2배로 잡히고 있었다. 🥲

두번째로 어디를 클릭하든 재귀함수를 돌아야 했기 때문에,
불필요한 작업이 추가되어 확실히 성능상에도 좋지 못할 것이다.
(물론 보통 depth는 깊어봤자 20-30개이기 때문에, 체감상 느껴지는 성능이슈는 발생하지 않았다)

이런 문제를 해결하는 데에 GTM에서 제공하는 방법이 있는데 내가 못 찾은 것일 수도 있지만,
발견하지 못했고, 현재까지의 고민이 내가 할 수 있는 최대였다.

0개의 댓글