メインコンテンツへスキップ
10分で読めます

カスタムフレームワーク

カスタムフレームワーク、ビルドツール、独自アーキテクチャに Zenovay アナリティクスを統合します。このガイドでは、公式インテグレーションでカバーされていないあらゆるセットアップのパターンを説明します。


クイックリファレンス

インテグレーションタイプ複雑さ最適な用途
スクリプトタグシンプル静的サイト、基本的なセットアップ
JavaScript API中程度SPA、動的ルーティング
HTTP API高度サーバーサイド、ヘッドレス、IoT
ファーストパーティプロキシ高度広告ブロッカー回避

コアインテグレーション方法

方法 1:スクリプトタグ(最もシンプル)

任意の HTML ページにトラッキングスクリプトを追加します。

<!DOCTYPE html>
<html>
<head>
    <title>Your Site</title>
    <!-- Zenovay Analytics -->
    <script defer data-tracking-code="YOUR_TRACKING_CODE" src="https://api.zenovay.com/z.js"></script>
</head>
<body>
    <!-- Your content -->
</body>
</html>

属性

属性必須説明
data-tracking-code必須トラッキングコード
defer推奨ノンブロッキング読み込み
data-api-url任意ファーストパーティプロキシ URL
data-auto任意false に設定するとページビューの自動トラッキングを無効にします

方法 2:JavaScript API

プログラムによる制御が必要な場合:

// Queue function (works before script loads)
window.zenovay = window.zenovay || function() {
    (window.zenovay.q = window.zenovay.q || []).push(arguments);
};

// Track page views manually
zenovay('page');

// Track custom events
zenovay('track', 'button_click', {
    button_name: 'signup',
    location: 'header'
});

// Identify users
zenovay('identify', 'user_123', {
    email: '[email protected]',
    plan: 'pro'
});

方法 3:HTTP API

サーバーサイドまたはブラウザ以外の環境向け:

// Node.js example
const response = await fetch('https://api.zenovay.com/e/YOUR_TRACKING_CODE', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-Forwarded-For': clientIp // Important for geo
    },
    body: JSON.stringify({
        event: 'pageview',
        url: 'https://yoursite.com/page',
        referrer: 'https://google.com',
        user_agent: clientUserAgent,
        timestamp: new Date().toISOString()
    })
});

完全な API ドキュメントについては API エンドポイントを参照してください。


シングルページアプリケーション(SPA)

SPA ではルート変更時にページビューを手動でトラッキングする必要があります。

汎用 SPA パターン

// Create a tracking wrapper
class ZenovayTracker {
    constructor(siteId) {
        this.siteId = siteId;
        this.previousPath = null;
    }

    init() {
        // Queue function (works before script loads)
        window.zenovay = window.zenovay || function() {
            (window.zenovay.q = window.zenovay.q || []).push(arguments);
        };
    }

    trackPageView(path) {
        // Prevent duplicate tracking
        if (path === this.previousPath) return;
        this.previousPath = path;

        zenovay('page');
    }

    trackEvent(name, properties = {}) {
        zenovay('track', name, properties);
    }
}

// Usage
const tracker = new ZenovayTracker('YOUR_TRACKING_CODE');
tracker.init();

// On route change
router.on('change', (route) => {
    tracker.trackPageView(route.path);
});

History API インテグレーション

History API を使用する SPA 向け:

// Intercept pushState and replaceState
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;

history.pushState = function(...args) {
    originalPushState.apply(this, args);
    window.dispatchEvent(new Event('locationchange'));
};

history.replaceState = function(...args) {
    originalReplaceState.apply(this, args);
    window.dispatchEvent(new Event('locationchange'));
};

// Also handle popstate (back/forward)
window.addEventListener('popstate', () => {
    window.dispatchEvent(new Event('locationchange'));
});

// Track on location change
window.addEventListener('locationchange', () => {
    zenovay('page');
});

ハッシュベースのルーティング

ハッシュルーティングを使用するアプリ向け:

window.addEventListener('hashchange', () => {
    zenovay('page');
});

// Initial page view
document.addEventListener('DOMContentLoaded', () => {
    zenovay('page');
});

静的サイトジェネレーター

Eleventy (11ty)

_includes/analytics.njk を作成します:

<!-- Zenovay Analytics -->
<script defer data-tracking-code="{{ site.zenovayId }}" src="https://api.zenovay.com/z.js"></script>

ベーステンプレートに追加します:

<!DOCTYPE html>
<html>
<head>
    {% include "analytics.njk" %}
</head>
<body>
    {{ content | safe }}
</body>
</html>

_data/site.js に記述します:

module.exports = {
    zenovayId: process.env.ZENOVAY_TRACKING_CODE || 'YOUR_TRACKING_CODE'
};

Hugo

layouts/partials/analytics.html を作成します:

{{ if not .Site.IsServer }}
<script defer data-tracking-code="{{ .Site.Params.zenovayId }}" src="https://api.zenovay.com/z.js"></script>
{{ end }}

config.toml に記述します:

[params]
zenovayId = "YOUR_TRACKING_CODE"

layouts/_default/baseof.html にインクルードします:

<head>
    {{ partial "analytics.html" . }}
</head>

Jekyll

_includes/analytics.html を作成します:

{% if jekyll.environment == "production" %}
<script defer data-tracking-code="{{ site.zenovay_id }}" src="https://api.zenovay.com/z.js"></script>
{% endif %}

_config.yml に記述します:

zenovay_id: "YOUR_TRACKING_CODE"

Gatsby

gatsby-ssr.js を作成します:

import React from "react";

export const onRenderBody = ({ setHeadComponents }) => {
    if (process.env.NODE_ENV === "production") {
        setHeadComponents([
            <script
                key="zenovay"
                defer
                data-tracking-code={process.env.GATSBY_ZENOVAY_ID}
                src="https://api.zenovay.com/z.js"
            />
        ]);
    }
};

ルート変更のために gatsby-browser.js に記述します:

export const onRouteUpdate = ({ location }) => {
    if (typeof window.zenovay !== 'undefined') {
        window.zenovay('page');
    }
};

Remix

app/root.tsx に追加します:

import { Scripts } from "@remix-run/react";

export default function App() {
    return (
        <html>
            <head>
                {process.env.NODE_ENV === "production" && (
                    <script
                        defer
                        data-tracking-code={process.env.ZENOVAY_ID}
                        src="https://api.zenovay.com/z.js"
                    />
                )}
            </head>
            <body>
                <Outlet />
                <Scripts />
            </body>
        </html>
    );
}

プログレッシブウェブアプリ(PWA)

インストールのトラッキング

// Track PWA installation
window.addEventListener('beforeinstallprompt', (e) => {
    zenovay('track', 'pwa_install_prompt_shown');
});

window.addEventListener('appinstalled', () => {
    zenovay('track', 'pwa_installed', {
        timestamp: new Date().toISOString()
    });
});

オフラインキュー

オフライン中にイベントをトラッキングし、オンラインになったときに送信します:

class OfflineQueue {
    constructor() {
        this.storageKey = 'zenovay_offline_queue';
        this.init();
    }

    init() {
        // Send queued events when coming online
        window.addEventListener('online', () => this.flush());

        // Check if we need to flush on load
        if (navigator.onLine) {
            this.flush();
        }
    }

    getQueue() {
        try {
            return JSON.parse(localStorage.getItem(this.storageKey)) || [];
        } catch {
            return [];
        }
    }

    saveQueue(queue) {
        localStorage.setItem(this.storageKey, JSON.stringify(queue));
    }

    add(event, properties) {
        const queue = this.getQueue();
        queue.push({
            event,
            properties,
            timestamp: new Date().toISOString(),
            url: window.location.href
        });
        this.saveQueue(queue);

        if (navigator.onLine) {
            this.flush();
        }
    }

    async flush() {
        const queue = this.getQueue();
        if (queue.length === 0) return;

        const successful = [];

        for (const item of queue) {
            try {
                zenovay('track', item.event, {
                    ...item.properties,
                    _queued_at: item.timestamp,
                    _sent_at: new Date().toISOString()
                });
                successful.push(item);
            } catch (error) {
                console.error('Failed to send queued event:', error);
                break; // Stop on first failure
            }
        }

        // Remove successfully sent items
        const remaining = queue.filter(item => !successful.includes(item));
        this.saveQueue(remaining);
    }
}

// Usage
const offlineQueue = new OfflineQueue();

// Track event (works offline)
offlineQueue.add('page_view', { path: '/home' });

サービスワーカーインテグレーション

サービスワーカー内:

// sw.js
self.addEventListener('sync', (event) => {
    if (event.tag === 'zenovay-sync') {
        event.waitUntil(syncAnalytics());
    }
});

async function syncAnalytics() {
    const cache = await caches.open('zenovay-analytics');
    const requests = await cache.keys();

    for (const request of requests) {
        try {
            await fetch(request);
            await cache.delete(request);
        } catch {
            // Will retry on next sync
        }
    }
}

Web コンポーネント

トラッキング付きカスタム要素

class TrackableButton extends HTMLElement {
    static get observedAttributes() {
        return ['track-event', 'track-properties'];
    }

    connectedCallback() {
        this.addEventListener('click', this.handleClick.bind(this));
    }

    handleClick() {
        const eventName = this.getAttribute('track-event') || 'button_click';
        let properties = {};

        try {
            properties = JSON.parse(this.getAttribute('track-properties') || '{}');
        } catch {}

        if (window.zenovay) {
            zenovay('track', eventName, {
                ...properties,
                element: this.tagName.toLowerCase(),
                text: this.textContent
            });
        }
    }
}

customElements.define('trackable-button', TrackableButton);

使用例:

<trackable-button
    track-event="cta_click"
    track-properties='{"location": "hero", "variant": "primary"}'>
    Get Started
</trackable-button>

Shadow DOM に関する考慮事項

Shadow DOM を使用する場合、シャドウルートにイベントリスナーをアタッチします:

class TrackedComponent extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
    }

    connectedCallback() {
        this.render();
        this.setupTracking();
    }

    render() {
        // Create button element safely
        const button = document.createElement('button');
        button.id = 'cta';
        button.textContent = 'Click Me';
        this.shadowRoot.appendChild(button);
    }

    setupTracking() {
        // Track clicks within shadow DOM
        this.shadowRoot.addEventListener('click', (e) => {
            if (e.target.id === 'cta') {
                zenovay('track', 'shadow_button_click', {
                    component: 'tracked-component'
                });
            }
        });

        // Track visibility with Intersection Observer
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    zenovay('track', 'component_viewed', {
                        component: 'tracked-component'
                    });
                    observer.disconnect();
                }
            });
        });

        observer.observe(this);
    }
}

ビルドツールのインテグレーション

Vite プラグイン

vite-plugin-zenovay.js を作成します:

export default function zenovayPlugin(options = {}) {
    const { siteId, enabled = true } = options;

    return {
        name: 'vite-plugin-zenovay',
        transformIndexHtml(html) {
            if (!enabled || !siteId) return html;

            const script = `<script defer data-tracking-code="${siteId}" src="https://api.zenovay.com/z.js"></script>`;

            return html.replace('</head>', `${script}</head>`);
        }
    };
}

// Usage in vite.config.js
import zenovayPlugin from './vite-plugin-zenovay';

export default {
    plugins: [
        zenovayPlugin({
            siteId: process.env.VITE_ZENOVAY_ID,
            enabled: process.env.NODE_ENV === 'production'
        })
    ]
};

Webpack プラグイン

webpack-zenovay-plugin.js を作成します:

const HtmlWebpackPlugin = require('html-webpack-plugin');

class ZenovayWebpackPlugin {
    constructor(options = {}) {
        this.options = options;
    }

    apply(compiler) {
        compiler.hooks.compilation.tap('ZenovayPlugin', (compilation) => {
            HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(
                'ZenovayPlugin',
                (data, cb) => {
                    if (this.options.siteId && this.options.enabled !== false) {
                        const script = `<script defer data-tracking-code="${this.options.siteId}" src="https://api.zenovay.com/z.js"></script>`;
                        data.html = data.html.replace('</head>', `${script}</head>`);
                    }
                    cb(null, data);
                }
            );
        });
    }
}

module.exports = ZenovayWebpackPlugin;

// Usage in webpack.config.js
const ZenovayPlugin = require('./webpack-zenovay-plugin');

module.exports = {
    plugins: [
        new HtmlWebpackPlugin(),
        new ZenovayPlugin({
            siteId: process.env.ZENOVAY_ID,
            enabled: process.env.NODE_ENV === 'production'
        })
    ]
};

Rollup プラグイン

// rollup-plugin-zenovay.js
export default function zenovayPlugin(options = {}) {
    return {
        name: 'zenovay',
        generateBundle(outputOptions, bundle) {
            for (const fileName in bundle) {
                if (fileName.endsWith('.html')) {
                    const chunk = bundle[fileName];
                    if (chunk.type === 'asset') {
                        const script = `<script defer data-tracking-code="${options.siteId}" src="https://api.zenovay.com/z.js"></script>`;
                        chunk.source = chunk.source.replace('</head>', `${script}</head>`);
                    }
                }
            }
        }
    };
}

サーバーサイドトラッキング

Node.js SDK パターン

// zenovay-server.js
class ZenovayServer {
    constructor(siteId, options = {}) {
        this.siteId = siteId;
        this.apiUrl = options.apiUrl || 'https://api.zenovay.com/e/';
        this.queue = [];
        this.flushInterval = options.flushInterval || 5000;
        this.batchSize = options.batchSize || 10;

        this.startFlushTimer();
    }

    startFlushTimer() {
        setInterval(() => this.flush(), this.flushInterval);
    }

    track(event, properties = {}, context = {}) {
        this.queue.push({
            event,
            properties,
            user_agent: context.userAgent,
            ip: context.ip,
            url: context.url,
            referrer: context.referrer,
            timestamp: new Date().toISOString()
        });

        if (this.queue.length >= this.batchSize) {
            this.flush();
        }
    }

    async flush() {
        if (this.queue.length === 0) return;

        const events = this.queue.splice(0, this.batchSize);

        try {
            await fetch(this.apiUrl + this.siteId, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ events })
            });
        } catch (error) {
            // Re-queue on failure
            this.queue.unshift(...events);
            console.error('Zenovay: Failed to send events', error);
        }
    }

    // Express middleware
    middleware() {
        return (req, res, next) => {
            req.trackEvent = (event, properties) => {
                this.track(event, properties, {
                    userAgent: req.headers['user-agent'],
                    ip: req.ip,
                    url: req.originalUrl,
                    referrer: req.headers.referer
                });
            };
            next();
        };
    }
}

module.exports = ZenovayServer;

// Usage
const ZenovayServer = require('./zenovay-server');
const zenovay = new ZenovayServer('YOUR_TRACKING_CODE');

app.use(zenovay.middleware());

app.get('/checkout', (req, res) => {
    req.trackEvent('checkout_started', {
        cart_value: 99.99
    });
    res.render('checkout');
});

Python の例

import requests
from datetime import datetime
from threading import Thread
from queue import Queue

class ZenovayTracker:
    def __init__(self, tracking_code, api_url='https://api.zenovay.com/e/'):
        self.tracking_code = tracking_code
        self.api_url = api_url
        self.queue = Queue()
        self._start_worker()

    def _start_worker(self):
        def worker():
            while True:
                event = self.queue.get()
                try:
                    requests.post(self.api_url + self.tracking_code, json=event, timeout=5)
                except Exception as e:
                    print(f"Zenovay error: {e}")
                self.queue.task_done()

        thread = Thread(target=worker, daemon=True)
        thread.start()

    def track(self, event, properties=None, context=None):
        context = context or {}
        self.queue.put({
            'event': event,
            'properties': properties or {},
            'user_agent': context.get('user_agent'),
            'ip': context.get('ip'),
            'url': context.get('url'),
            'timestamp': datetime.utcnow().isoformat()
        })

# Flask example
from flask import Flask, request

app = Flask(__name__)
tracker = ZenovayTracker('YOUR_TRACKING_CODE')

@app.route('/purchase', methods=['POST'])
def purchase():
    tracker.track('purchase', {
        'revenue': request.json.get('total'),
        'currency': 'USD'
    }, {
        'user_agent': request.headers.get('User-Agent'),
        'ip': request.remote_addr,
        'url': request.url
    })
    return {'status': 'ok'}

テストとデバッグ

デバッグモード

// Enable debug mode
zenovay('debug');

// All events will be logged to console
zenovay('track', 'test_event', { test: true });
// Console: [Zenovay] Event tracked: test_event { test: true }

テスト用モックトラッカー

// __mocks__/zenovay.js
const mockZenovay = {
    calls: [],

    handler(...args) {
        mockZenovay.calls.push(args);
    },

    reset() {
        mockZenovay.calls = [];
    },

    getEvents() {
        return mockZenovay.calls.filter(c => c[0] === 'track');
    }
};

window.zenovay = (...args) => mockZenovay.handler(...args);
window.zenovay.mock = mockZenovay;

// In your test
test('tracks button click', () => {
    window.zenovay.mock.reset();

    fireEvent.click(getByRole('button', { name: 'Sign Up' }));

    expect(window.zenovay.mock.getEvents()).toContainEqual(
        ['track', 'signup_click', { location: 'header' }]
    );
});

ネットワーク検査

トラッキングが機能しているか確認するには:

  1. DevTools → ネットワークタブを開きます
  2. zenovay または z.js でフィルタリングします
  3. サイト内をクリックして操作します
  4. api.zenovay.com へのリクエストを確認します

コンテンツセキュリティポリシー

Zenovay を許可するように CSP ヘッダーを設定します:

Content-Security-Policy:
    default-src 'self';
    script-src 'self' https://api.zenovay.com;
    connect-src 'self' https://api.zenovay.com;

ファーストパーティトラッキング(推奨)の場合:

Content-Security-Policy:
    default-src 'self';
    script-src 'self';
    connect-src 'self';

ベストプラクティス

  1. 非同期で読み込む:ブロッキングを防ぐために常に defer または async を使用する
  2. エラーを処理する:耐障害性のためにトラッキング呼び出しを try-catch でラップする
  3. プライバシーを尊重する:トラッキング前に同意を確認し、DNT を尊重する
  4. イベントをバッチ処理する:リクエストを減らすために可能な限りイベントをまとめる
  5. 十分にテストする:複数のブラウザとデバイスでトラッキングを検証する
  6. ファーストパーティを使用する:精度向上のためにファーストパーティトラッキングを設定する
  7. パフォーマンスを監視する:トラッキングが Core Web Vitals に影響しないことを確認する

関連リソース


インテグレーションでサポートが必要ですか? [email protected] までご連絡ください。

このページは役に立ちましたか?