사용자의 검색 기록을 저장하고 분석하려고 서버에 logstash를 도입했는데 회원 뿐 아니라 비회원 사용자 식별을 하려면 각 사용자를 식별하는 값이 필요했다. 각 세션마다 UUID 같은 걸로 세션 id를 클라이언트에서 만들거나 GA에서 이미 사용자 식별값으로 쓰고 있는 user_pseudo_id를 쓰자거나 하는 등 여러 아이디어를 내면서 팀원들과 논의를 해봤는데, 세션마다 바뀌는 세션 id보다는 사용자를 식별할 수 있는 반영구적인 값이 필요했다.
그래서 user_pseudo_id를 쓰려고 했으나 이 값은 오직 BigQuery로만 접근할 수 있어서 클라이언트에서는 가져올 방법이 없었다. 게다가 GA4에서는 예전 UA(GA3)에서 제공하는 client_id를 사용하지 않기 때문에 이 값이 있어도 무시한단다🙃
하지만 GTM에서 사용자 변수로 client_id를 등록할 수 있으면 이벤트 수집시 client_id도 수집이 되니까 client_id를 GTM에도 변수로 등록하고, 서버로도 이 값을 보내주면 매칭해서 사용할 수 있겠다는 생각이 들었다.
client_id는 _ga 쿠키에 들어있는 값에서 추출할 수 있다고 하니까 쿠키를 삭제하거나 GA(GTM) initialize 타이밍이 안 맞아서 유실될 수도 있고 여러 가지로 안정적인 건 아니지만 그래도 최대한 활용할 수 있는 값인 것 같아서 요걸 사용하기로 결론이 내려졌다.
따라서 이번에는 Next.js v14 (App Router) 프로젝트에서 GTM을 이용해 client_id를 추출하고, 이 값을 API 요청에 커스텀 헤더로 실어서 서버로 보내는 방법을 정리해본다.
client_id는 어떻게 다뤄지는가?client_id 가져오는 방법client_id 포함하기client_id는 어떻게 다뤄지는가?GA4는 UA(GA3)랑 다르게 client_id를 노출해주진 않지만, _ga 쿠키에는 여전히 이 값이 포함돼 있다.
예시:
_ga=GA1.1.1234567890.9876543210
여기서 뒤에 있는 1234567890.9876543210 이 부분이 client_id다.
GPT가 알려준 이걸 추출하는 자바스크립트 코드 예시는 다음과 같다:
function getGA4ClientId() {
const gaCookie = document.cookie
.split('; ')
.find(row => row.startsWith('_ga='));
if (!gaCookie) return null;
const parts = gaCookie.split('.');
if (parts.length >= 4) {
return `${parts[2]}.${parts[3]}`;
}
return null;
}
client_id 추출 변수 만들기GTM을 쓰고 있다면, 사용자 정의 변수로 client_id를 뽑아낼 수 있다.
function() {
var gaCookie = document.cookie
.split('; ')
.find(function(row) { return row.startsWith('_ga='); });
if (!gaCookie) return null;
var parts = gaCookie.split('.');
if (parts.length >= 4) {
return parts[2] + '.' + parts[3];
}
return null;
}
client_id 이런 식으로 지정client_id 가져오는 방법App Router 기준으로, 클라이언트에서 _ga 쿠키를 읽어서 client_id를 추출하면 된다.
import { cookies } from 'next/headers';
export function getClientId() {
const gaCookie = cookies().get('_ga')?.value;
if (!gaCookie) return;
const parts = gaCookie.split('.');
if (parts.length >= 4) {
return `${parts[2]}.${parts[3]}`;
}
return null;
}
client_id 포함하기검색 API 요청을 할 때 client_id를 커스텀 헤더로 같이 보내면, 서버에서 이걸 기준으로 로그를 남길 수 있다.
'use client';
import { getClientId } from './cookies.util.ts';
export function SearchComponent() {
const clientId = useClientId();
const handleSearch = async (query: string) => {
const clientId = await getClientId();
if (!clientId) return;
await fetch('/api/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Client-ID': clientId,
},
body: JSON.stringify({ query }),
});
};
return (
<button onClick={() => handleSearch('Next.js')}>
검색
</button>
);
}
Next.js + GTM + GA4 조합에서 client_id를 추출해서 GTM에 등록하고 서버에 전송하는 전체 흐름을 정리해보았다.
이렇게 하면 사용자 분석에도 좀 더 도움 되겠..지...?
그랬으면 좋겠다ㅎㅎㅎㅎ