React SPA에서 Google Analytics 4로 페이지 뷰 추적
Source: Dev.to

표준 HTML 웹사이트에 Google Analytics (GA4)를 추가하는 것은 간단합니다: 추적 스니펫을 <head>에 붙여넣기만 하면 끝납니다. 사용자가 링크를 클릭할 때마다 브라우저는 새로운 HTML 페이지를 가져오고, GA는 페이지 뷰를 기록합니다.
하지만 React, Vite, 그리고 React Router를 사용해 싱글 페이지 애플리케이션 (SPA)을 구축하고 있다면, 이 기본 동작이 깨집니다.
React SPA에서는 링크를 클릭해도 페이지가 새로 고침되지 않습니다. React는 단순히 기존 컴포넌트를 언마운트하고 새 컴포넌트를 마운트하면서 브라우저의 URL 히스토리를 조작합니다. 페이지가 실제로 새로 고침되지 않기 때문에 Google Analytics는 새로운 URL을 기록하지 못하고, 분석 결과에서는 사용자가 영원히 홈 페이지에 머물고 있는 것처럼 보입니다.
아래는 제가 포트폴리오 사이트에 적용한 단계별 해결 방법입니다.
1. 환경 설정
트래킹 ID를 소스 코드에 하드코딩하지 마세요. GA 대시보드에서 확인할 수 있는 측정 ID(G-XXXXXXXXXX로 시작하는 경우가 대부분)를 .env 파일에 추가합니다:
VITE_GA_TRACKING_ID=G-**********
2. 초기 HTML 스니펫 (수정됨)
index.html에 기본 Google Analytics 추적 코드를 포함하되, 자동 page_view 추적을 비활성화하여 중복 집계를 방지합니다.
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){ dataLayer.push(arguments); }
gtag('js', new Date());
// IMPORTANT: Disable the default page_view tracking here!
gtag('config', '%VITE_GA_TRACKING_ID%', { send_page_view: false });
</script>
Vite에 대한 참고: %VITE_GA_TRACKING_ID%는 빌드 시 환경 변수를 주입합니다.
3. 라우트 리스너 컴포넌트 만들기
react-router-dom의 useLocation을 사용하여 URL 변경을 감지하는 가볍고 보이지 않는 컴포넌트를 생성합니다.
src/components/Analytics.tsx
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
// Extend window object for TypeScript
declare global {
interface Window {
gtag: (...args: any[]) => void;
}
}
export const Analytics = () => {
const location = useLocation();
useEffect(() => {
const GA_MEASUREMENT_ID = import.meta.env.VITE_GA_TRACKING_ID;
if (GA_MEASUREMENT_ID && typeof window.gtag === 'function') {
window.gtag('config', GA_MEASUREMENT_ID, {
page_path: location.pathname + location.search,
});
}
}, [location]);
return null; // Invisible component
};
4. 라우터에 연결하기
<Analytics />를 <BrowserRouter> 내부에, <Routes> 블록 외부에 마운트합니다.
src/index.tsx
import { Analytics } from './components/Analytics';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { HelmetProvider } from 'react-helmet-async';
import { Suspense } from 'react';
import ScrollToTop from './components/ScrollToTop';
import LoadingFallback from './components/LoadingFallback';
import HomePage from './pages/HomePage';
import AboutPage from './pages/AboutPage';
// ...other imports
const App = () => {
return (
<HelmetProvider>
<BrowserRouter>
{/* Place the listener here! */}
<Analytics />
<ScrollToTop />
<Suspense fallback={<LoadingFallback />}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
{/* ... other routes */}
</Routes>
</Suspense>
</BrowserRouter>
</HelmetProvider>
);
};
export default App;
5. 결과
- 사용자가
vicentereyes.org에 접속하면 GA 스크립트가 로드됩니다. - React가 부팅되고 라우터가 마운트되며
<Analytics />컴포넌트가/에 대한 페이지 뷰 이벤트를 발생시킵니다. - 이후 탐색(예: “Projects” 클릭) 시 URL이 업데이트되고
<Analytics />내부의useEffect가 트리거되어/projects에 대한 깔끔한 페이지 뷰 이벤트를 전송합니다.
이렇게 하면 무거운 외부 라이브러리 없이 정확한 SPA 추적이 가능합니다.