メインコンテンツへスキップ
無料15 minutes中級

Next.js統合

ZenovayとNext.jsを統合する — App Router、Pages Router、サーバーサイドトラッキングのサポート。このAPIインテグレーションガイドでnextjsについて学びます。

nextjsreactintegrationssrapp-router
最終更新日:

トラッキングスクリプトタグを使用して、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キーで認証します。各イベントには、typepagevieweventidentifygoal、または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 visitorIdsessionIdが必要です(列はUUID型です)。そのため、通常はブラウザトラッカーに任せるのが最適です。サーバーエンドポイントはeventidentifygoal、および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"
/>

次のステップ

この記事は役に立ちましたか?