トラッキングスクリプトタグを使用して、Next.jsアプリケーションにZenovayアナリティクスを統合します。App RouterとPages Routerの両方をサポートしています。
インストール
npmパッケージは不要です。Next.jsのScriptコンポーネントまたは標準の<script>タグを使用してZenovayトラッキングスクリプトを追加します。
Zenovayアプリでトラッキングコードを見つけることができます:Domainsを開き、サイトをクリックしてからGeneral設定ページを開きます。Tracking scriptカードはスニペットとVerify Installationボタンを表示します。
App Routerのセットアップ(Next.js 13以降)
ルートレイアウトへの追加
// app/layout.tsx
import Script from 'next/script';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="YOUR_TRACKING_CODE"
strategy="afterInteractive"
/>
</body>
</html>
);
}
環境変数を使用する場合
// app/layout.tsx
import Script from 'next/script';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code={process.env.NEXT_PUBLIC_ZENOVAY_TRACKING_CODE}
strategy="afterInteractive"
/>
</body>
</html>
);
}
イベント用クライアントコンポーネント
クライアント側(SPA)ナビゲーションでページビューをトラックするクライアントコンポーネントを作成します:
// components/Analytics.tsx
'use client';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect } from 'react';
export function Analytics() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
// Track page view on route change (SPA navigation)
if (window.zenovay) {
window.zenovay('page');
}
}, [pathname, searchParams]);
return null;
}
レイアウトに追加します:
// app/layout.tsx
import Script from 'next/script';
import { Analytics } from '@/components/Analytics';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<Analytics />
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="YOUR_TRACKING_CODE"
strategy="afterInteractive"
/>
</body>
</html>
);
}
Pages Routerのセットアップ
_app.tsxへの追加
// pages/_app.tsx
import Script from 'next/script';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="YOUR_TRACKING_CODE"
strategy="afterInteractive"
/>
</>
);
}
または_document.tsxでScriptを使用する
// pages/_document.tsx
import { Html, Head, Main, NextScript } from 'next/document';
import Script from 'next/script';
export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="YOUR_TRACKING_CODE"
strategy="afterInteractive"
/>
</body>
</Html>
);
}
イベントトラッキング
クライアントコンポーネント
'use client';
export function SignupButton() {
const handleClick = () => {
if (window.zenovay) {
window.zenovay('track', 'signup_click', {
plan: 'pro',
source: 'pricing'
});
}
};
return (
<button onClick={handleClick}>
Start Free Trial
</button>
);
}
ユーザー識別
認証後
'use client';
import { useEffect } from 'react';
import { useSession } from 'next-auth/react';
export function UserIdentifier() {
const { data: session } = useSession();
useEffect(() => {
if (session?.user && window.zenovay) {
window.zenovay('identify', session.user.id, {
email: session.user.email,
name: session.user.name
});
}
}, [session]);
return null;
}
収益トラッキング
e-commerceチェックアウト
'use client';
import { useEffect } from 'react';
export function OrderConfirmation({ order }) {
useEffect(() => {
if (window.zenovay) {
window.zenovay('revenue', order.total, 'USD', {
order_id: order.id,
items: order.items
});
}
}, [order.id]);
return (
<div>
<h1>Order Confirmed!</h1>
</div>
);
}
情報
バックエンド(例:Stripeウェブフック)で取得した購入の場合は、サーバーから収益を報告してください —以下のServer-Side Trackingを参照してください。これにより、ブラウザが確認ページに到達しない場合でも、収益の帰属を正確に保つことができます。
サーバーサイドトラッキング
バックエンドが信頼できるソースであるイベント(購入、サインアップ、サイト外のアクティビティ)の場合は、**POST /api/v1/events**エンドポイントを使用してサーバーから送信してください。これは別の認証された取り込みAPIです。
警告
サーバーサイド取り込みにはAPIキーと有料プラン(Pro、Scale、またはEnterprise)が必要です。無料アカウントは403 API_PAID_PLAN_REQUIREDを受け取ります。Settings → Security → API keysでキーを作成してください — Getting an API Keyを参照してください。上記のブラウザトラッキングスクリプトはすべてのプランで無料のままです。
Authorization: Bearerヘッダーでzv_* APIキーで認証します。各イベントには、type(pageview、event、identify、goal、またはpurchase)、ミリ秒単位のts、およびタイプ固有のpropsオブジェクトがあります。再試行を安全にするためにidempotencyKeyを渡します。
Server Action
// app/actions.ts
'use server';
export async function submitForm(formData: FormData) {
// Process form
const email = formData.get('email');
// Track a custom event server-side
await fetch('https://api.zenovay.com/api/v1/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.ZENOVAY_API_KEY}`,
},
body: JSON.stringify({
trackingCode: process.env.ZENOVAY_TRACKING_CODE,
events: [{
type: 'event',
ts: Date.now(),
props: {
name: 'form_submitted',
form: 'contact',
has_email: !!email,
},
idempotencyKey: `form-${crypto.randomUUID()}`,
}],
}),
});
return { success: true };
}
APIルートハンドラー
// app/api/track/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const body = await request.json();
await fetch('https://api.zenovay.com/api/v1/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.ZENOVAY_API_KEY}`,
},
body: JSON.stringify({
trackingCode: process.env.ZENOVAY_TRACKING_CODE,
events: [{
type: 'event',
ts: Date.now(),
props: {
name: body.name || 'custom_event',
...body.props,
},
idempotencyKey: crypto.randomUUID(),
}],
serverContext: {
clientIp: request.headers.get('x-forwarded-for') ?? undefined,
userAgent: request.headers.get('user-agent') ?? undefined,
},
})
});
return NextResponse.json({ success: true });
}
情報
pageviewイベントは実際のRFC-4122 visitorIdとsessionIdが必要です(列はUUID型です)。そのため、通常はブラウザトラッカーに任せるのが最適です。サーバーエンドポイントはevent、identify、goal、およびpurchaseタイプに最適です。完全なイベントスキーマと拒否理由については、Server-Side Trackingを参照してください。
ウェブフックからの購入
// app/api/webhooks/stripe/route.ts
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const event = await request.json();
// ... verify the Stripe signature first ...
await fetch('https://api.zenovay.com/api/v1/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.ZENOVAY_API_KEY}`,
},
body: JSON.stringify({
trackingCode: process.env.ZENOVAY_TRACKING_CODE,
events: [{
type: 'purchase',
ts: Date.now(),
// The visitorId you stored against this customer on your side
visitorId: event.data.object.metadata?.visitor_id,
props: {
amount: event.data.object.amount_total / 100,
currency: event.data.object.currency?.toUpperCase() || 'USD',
payment_provider: 'stripe',
},
idempotencyKey: event.id,
}],
})
});
return NextResponse.json({ received: true });
}
環境変数
セットアップ
# .env.local
NEXT_PUBLIC_ZENOVAY_TRACKING_CODE=your-tracking-code # client-side script (public)
ZENOVAY_TRACKING_CODE=your-tracking-code # server-side (same value)
ZENOVAY_API_KEY=zv_your-api-key # server-side only — never expose
警告
トラッキングコード(data-tracking-code属性で使用)のみがNEXT_PUBLIC_プレフィックスで公開するのに安全です。zv_* APIキーはサーバー側にとどまる必要があります —NEXT_PUBLIC_で接頭辞を付けないでください。
使用方法
// Client-side (in layout)
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code={process.env.NEXT_PUBLIC_ZENOVAY_TRACKING_CODE}
strategy="afterInteractive"
/>
// Server-side (in API routes or server actions)
const response = await fetch('https://api.zenovay.com/api/v1/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.ZENOVAY_API_KEY}`,
},
body: JSON.stringify({
trackingCode: process.env.ZENOVAY_TRACKING_CODE,
events: [{ type: 'event', ts: Date.now(), props: { name: 'custom_event' } }],
}),
});
ルートグループアナリティクス
// app/(marketing)/layout.tsx
import Script from 'next/script';
export default function MarketingLayout({ children }) {
return (
<>
{children}
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="MARKETING_TRACKING_CODE"
strategy="afterInteractive"
/>
</>
);
}
// app/(app)/layout.tsx
import Script from 'next/script';
export default function AppLayout({ children }) {
return (
<>
{children}
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="APP_TRACKING_CODE"
strategy="afterInteractive"
/>
</>
);
}
TypeScript型定義
// types/zenovay.d.ts
interface ZenovayFunction {
(command: 'track', name: string, properties?: Record<string, unknown>): void;
(command: 'identify', userId: string, traits?: Record<string, unknown>): void;
(command: 'goal', name: string, properties?: Record<string, unknown>): void;
(command: 'page'): void;
(command: 'revenue', amount: number, currency: string, meta?: Record<string, unknown>): void;
}
declare global {
interface Window {
zenovay: ZenovayFunction;
}
}
export {};
一般的なパターン
Next-Authを使用する場合
// app/providers.tsx
'use client';
import { SessionProvider } from 'next-auth/react';
export function Providers({ children }) {
return (
<SessionProvider>
{children}
</SessionProvider>
);
}
ISR/SSGページ
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({ slug: post.slug }));
}
export default async function BlogPost({ params }) {
// Static generation - no server tracking here
// Tracking happens on client via the Zenovay script tag
const post = await getPost(params.slug);
return <Article post={post} />;
}
トラブルシューティング
スクリプトが読み込まれない
確認してください:
- スクリプトがレイアウト/ドキュメントに含まれているか
- 広告ブロッカーの干渉がないか
- トラッキングコードが正しいか
ページビューが記録されない
確認してください:
- スクリプトがbodyの後に読み込まれているか
- 開発モードにないか(意図的でない限り)
- ルート変更が検出されているか
ハイドレーションの問題
afterInteractiveストラテジーを使用してください:
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="YOUR_TRACKING_CODE"
strategy="afterInteractive"
/>