사용자 행동 데이터를 받아 프로젝트를 개선하기 위해 GA를 사용하려고 한다.
나는 현재 Next.js 14버전을 사용을 하고 있기 때문에 Next.js 14버전 공식 Docs에서 권장하고 있는 next/third-parties 를 사용한 구글 애널리틱스 설정을 하려고 한다.
Next JS 14 + TypeScript
현재 진행하는 청첩장 커스텀제작가능한 페이지를 만들면서 사용자들의 집계를 위해 구글 애널리틱스를 등록하게 되었다.
GA4와 GTM을 사용하여 데이터 통계를 받을 예정(Google Analytics 4(GA4)와 Google Tag Manager(GTM)
=> 웹사이트와 앱에서 사용자 행동을 추적하고 분석함
이벤트 기반 데이터 모델의 사용으로 사용자와의 상호작용을 추적, 웹과 앱데이터를 통합하여 분석
GTM의 다양한 태그 를 한 곳에서 관리하고 배포할 수 있는 태그 관리 시스템
구글 애널리틱스를 설정하는 방법에는 서드파티 라이브러리와 next/script 를 사용하는 방법이 있었다.
next/script는 Next.js에서 외부 JavaScript 파일을 안전하게 로드할 수 있는 방법을 제공합니다. 이 방법은 서버 측 렌더링(SSR) 환경을 고려해 최적화된 방식으로 외부 스크립트를 로드하는 방법
비동기 로딩, next/script를 사용하면 외부 스크립트를 비동기적으로 로드하여 페이지 로딩 성능에 미치는 영향을 최소화
로딩 전략 제어: strategy 속성을 사용하여 스크립트 로딩 시점을 제어할 수 있습니다. afterInteractive 전략을 사용하면 페이지가 인터랙티브해진 후에 스크립트가 로드
크립트의 순서 보장: 여러 스크립트를 로드할 때, 로드 순서를 보장
=> Next.js 어플리케이션에서 다양한 서드 파티 라이브러리들을 통합하고 성능을 최적화할 수 있도록 도와주는 라이브러리이다.
서드파티 => 다른 개발자나 회사가 만든 외부 라이브러리를 의미
ex) Google Analytics, Facebook SDK, YouTube API
두 가지 선택지 중 하나를 선택해야 하나 고민을 하던 중 next/script + react-ga4를 함께 사용하여 효율적으로 추적하는 방법이 있어 섞어 쓰는 방법을 선택하게 되었다. next/script로 GA4 스크립트를 로드한 후 react-ga4 라이브러리로 이벤트를 추적하는 방법을 사용했다.
NEXT_PUBLIC_GOOGLE_ANALYTICS =~~~~~~~
pnpm add react-ga4
// layout.tsx 또는 _app.tsx
import Script from 'next/script';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
{/* Google Analytics GA4 스크립트 로드 */}
<Script
src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GA_TRACKING_ID}`}
strategy="afterInteractive"
/>
<Script
id="ga-init"
strategy="afterInteractive"
>
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${process.env.NEXT_PUBLIC_GA_TRACKING_ID}', {
page_path: window.location.pathname,
});
`}
</Script>
</head>
<body>{children}</body>
</html>
);
}
// components/Analytics.tsx
import { useEffect } from 'react';
import ReactGA from 'react-ga4';
const useAnalytics = () => {
useEffect(() => {
// Google Analytics 초기화
ReactGA.initialize(process.env.NEXT_PUBLIC_GA_TRACKING_ID);
// 페이지 뷰 추적
ReactGA.send({ hitType: 'pageview', page: window.location.pathname });
}, []);
return null;
};
export default useAnalytics;
// layout.tsx 또는 _app.tsx
import Analytics from './components/Analytics';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<Analytics />
{children}
</body>
</html>
);
}
참고링크 3 : Next.js 프로젝트에 GA4 붙이기
나는 Next.js를 사용하고 있다. Next.js에서 head태그 직접 추가가 권장되지 않음으로 page > layout.tsx에서 metadata head 를 추가해주었다.
export const metadata: Metadata = {
metadataBase: new URL(process.env.NEXT_PUBLIC_DOMAIN as string),
openGraph: {
images: [
{
url: '/opengraph-image',
},
],
},
title: '드림카드 | 나만의 모바일 청첩장',
description: '모바일 청첩장을 직접 제작하고 커스텀할 수 있는 서비스입니다. 소중한 날을 더 특별하게 만들어보세요!',
verification: {
google: '값',
other: {
'naver-site-verification': '값',
},
},
};
검색 사이트의 크롤링 봇들이 특정 사이트에 대한 정보를 크롤링하는것을 막아야 한다.이런 역할을 하기 위해 사용되는 것으로 robots.txt 가 있다.robots.txt를 이용하면 크롤링을 해도 되는 주소와 하지 말아야 될 주소를 구분하여 명시해 줄 수 있다.
sitemap.xml을 만드는 방법에는 두 가지가 있고 차이점은 next-sitemap은모두 정적 페이지라는 것이라는 것이다.
즉, 다음과 같이 페이지의 경로에 동적으로 값이 변경되어 들어오는 페이지들에 대해서는 sitemap이 만들어지지 않는다는 것이다.
pnpm add next-sitemap
/** @type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: process.env.NEXT_PUBLIC_DOMAIN || 'https://yourdomain.com',
generateRobotsTxt: true, // robots.txt 생성 여부
sitemapSize: 7000, // sitemap 파일의 최대 URL 개수 (기본값: 5000)
exclude: ['/admin/*', '/api/*'], // 제외할 경로
robotsTxtOptions: {
policies: [
{
userAgent: '*',
allow: '/',
},
],
},
};
{
"scripts": {
"postbuild": "next-sitemap"
}
}
빌드 실행 후 자도으로 sitemap.xml파일 생성
import { MetadataRoute } from 'next';
export default function sitemap(): Promise<MetadataRoute.Sitemap> {
return [
{
url: 'http://www.dream-card.co.kr',
lastModified: new Date().toISOString().split('T')[0],
changeFrequency: 'daily',
priority: 1,
},
{
url: 'https://acme.com/create/card',
lastModified: new Date().toISOString().split('T')[0],
changeFrequency: 'always',
priority: 0.5,
},
];
}
import { getReview } from '@/utils/getReview';
import { MetadataRoute } from 'next';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const reviews = await getReview({ pageParam: 0, ROW: 100 });
const dynamicRoutes = reviews.map((review) => ({
url: `http://www.dream-card.co.kr/review/${review.id}`,
lastModified: new Date(review.created_at),
priority: 0.7,
}));
return [
{
url: 'http://www.dream-card.co.kr',
lastModified: new Date().toISOString().split('T')[0],
changeFrequency: 'daily',
priority: 1,
},
{
url: 'https://acme.com/create/card',
lastModified: new Date().toISOString().split('T')[0],
changeFrequency: 'always',
priority: 0.5,
},
...dynamicRoutes,
];
}
: 검색엔진 크롤러에게 웹 사이트의 어떤 부분을 크롤링 할 수 있고 어떤 부분의 접근을 제한할 지 알려주는 파일이다.
검색 엔진 크롤러가 사이트를 방문할 때 가장 먼저 읽는 파일이다.
import { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: '/my/',
},
sitemap: 'http://www.dream-card.co.kr/sitemap.xml',
};
}