Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.grainql.com/llms.txt

Use this file to discover all available pages before exploring further.

This guide covers both the App Router (Next.js 13+) and Pages Router (Next.js 12 and earlier).

Install the Package

npm
npm install @grainql/tag
yarn
yarn add @grainql/tag
pnpm
pnpm add @grainql/tag

App Router (Next.js 13+)

Create an Analytics Component

Grain Tag must be initialized on the client. Create a client component that runs once:
// app/grain.tsx
'use client';

import { useEffect } from 'react';
import { init, isInitialized } from '@grainql/tag';

export function GrainAnalytics() {
  useEffect(() => {
    if (!isInitialized()) {
      init({ tenantId: process.env.NEXT_PUBLIC_GRAIN_TENANT_ID! });
    }
  }, []);
  return null;
}

Add to Your Root Layout

// app/layout.tsx
import { GrainAnalytics } from './grain';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <GrainAnalytics />
        {children}
      </body>
    </html>
  );
}

Add Environment Variable

Create a .env.local file:
NEXT_PUBLIC_GRAIN_TENANT_ID=your-tenant-id
Replace your-tenant-id with the alias from your dashboard.

Use in Components

Now you can track events from any Client Component:
// app/page.tsx
'use client';

import { track } from '@grainql/tag';

export default function HomePage() {
  return (
    <div>
      <h1>Welcome!</h1>
      <button onClick={() => track('cta_clicked', { location: 'hero' })}>
        Get Started
      </button>
    </div>
  );
}
Server vs Client Components: track(), identify(), and other Grain Tag functions must be called from Client Components (with the 'use client' directive) or inside useEffect. Grain Tag is SSR safe — init() returns a no-op in non-browser environments — but tracking functions need the browser to work.
Automatic page views: Grain Tag hooks into the History API to track page views and navigation automatically. You do not need a page view tracker component — this works out of the box with Next.js App Router.

Identify Users

// Any client component
'use client';

import { identify, track } from '@grainql/tag';

export function LoginHandler() {
  const handleLogin = async (email: string, password: string) => {
    const user = await yourLoginFunction(email, password);
    identify(user.id);
    track('user_logged_in', { method: 'email' });
  };

  return <form onSubmit={handleLogin}>...</form>;
}
'use client';

import { getInstance } from '@grainql/tag';

export function ConsentBanner() {
  const handleAccept = () => {
    const grain = getInstance();
    grain?.consent.grant();
  };

  const handleDecline = () => {
    const grain = getInstance();
    grain?.consent.revoke();
  };

  return (
    <div>
      <p>We use analytics to improve your experience.</p>
      <button onClick={handleAccept}>Accept</button>
      <button onClick={handleDecline}>Decline</button>
    </div>
  );
}

Pages Router (Next.js 12)

Initialize in _app.tsx

// pages/_app.tsx
import type { AppProps } from 'next/app';
import { useEffect } from 'react';
import { init, isInitialized } from '@grainql/tag';

export default function App({ Component, pageProps }: AppProps) {
  useEffect(() => {
    if (!isInitialized()) {
      init({ tenantId: process.env.NEXT_PUBLIC_GRAIN_TENANT_ID! });
    }
  }, []);

  return <Component {...pageProps} />;
}

Add Environment Variable

Create a .env.local file:
NEXT_PUBLIC_GRAIN_TENANT_ID=your-tenant-id

Use in Pages

// pages/index.tsx
import { track } from '@grainql/tag';

export default function HomePage() {
  return (
    <div>
      <h1>Welcome!</h1>
      <button onClick={() => track('cta_clicked', { location: 'hero' })}>
        Get Started
      </button>
    </div>
  );
}
Automatic page views: Grain Tag automatically tracks navigation via the History API for both App Router and Pages Router. No manual page view tracking is needed.

Server-Side Tracking

For tracking events from API routes or Server Actions, use @grainql/analytics-web (a different package designed for server-side use):
npm install @grainql/analytics-web

API Routes

// app/api/checkout/route.ts (App Router)
// Server-side tracking uses @grainql/analytics-web (a different package)
import { createGrainAnalytics } from '@grainql/analytics-web';

const grain = createGrainAnalytics({
  tenantId: process.env.GRAIN_TENANT_ID!,
  authStrategy: 'SERVER_SIDE',
  secretKey: process.env.GRAIN_SECRET_KEY!
});

export async function POST(request: Request) {
  const body = await request.json();

  // Track server-side event
  await grain.track('checkout_completed', {
    order_id: body.orderId,
    total: body.total
  }, { flush: true }); // Flush immediately for serverless

  return Response.json({ success: true });
}
Serverless tip: Use { flush: true } to send events immediately before the function terminates.

Server Actions (App Router)

// app/actions.ts
'use server';

// Server-side tracking uses @grainql/analytics-web (a different package)
import { createGrainAnalytics } from '@grainql/analytics-web';

const grain = createGrainAnalytics({
  tenantId: process.env.GRAIN_TENANT_ID!,
  authStrategy: 'SERVER_SIDE',
  secretKey: process.env.GRAIN_SECRET_KEY!
});

export async function submitForm(formData: FormData) {
  // Your form logic...

  await grain.track('form_submitted', {
    form_name: 'contact'
  }, { flush: true });

  return { success: true };
}

What’s Next?

Core API Reference

See all available methods and options

User Identification

Track users across sessions

Event Best Practices

Learn what to track and how to structure events

Server-Side Setup

Advanced server-side configuration
Vercel deployment: All environment variables starting with NEXT_PUBLIC_ are automatically available in the browser. Keep secret keys (for server-side tracking) private by omitting the NEXT_PUBLIC_ prefix.
Need remote configuration or feature flags? See @grainql/analytics-web for remote config, React hooks (useConfig, useTrack, GrainProvider), and more.