This guide covers both the App Router (Next.js 13+) and Pages Router (Next.js 12 and earlier).
Install the Package
npm install @grainql/analytics-web
yarn add @grainql/analytics-web
pnpm add @grainql/analytics-web
App Router (Next.js 13+)
Create a Provider Component
The Grain provider uses React Context, so it needs to be a Client Component:
// app/providers.tsx
'use client' ;
import { GrainProvider } from '@grainql/analytics-web/react' ;
import { ReactNode } from 'react' ;
export function Providers ({ children } : { children : ReactNode }) {
return (
< GrainProvider
config = { {
tenantId: process . env . NEXT_PUBLIC_GRAIN_TENANT_ID ! ,
defaultConfigurations: {
hero_text: 'Welcome!'
}
} }
>
{ children }
</ GrainProvider >
);
}
Wrap Your Root Layout
// app/layout.tsx
import { Providers } from './providers' ;
export default function RootLayout ({
children ,
} : {
children : React . ReactNode ;
}) {
return (
< html lang = "en" >
< body >
< Providers >
{ children }
</ Providers >
</ 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 use Grain hooks in any Client Component:
// app/page.tsx
'use client' ;
import { useTrack , useConfig } from '@grainql/analytics-web/react' ;
export default function HomePage () {
const track = useTrack ();
const { value : heroText } = useConfig ( 'hero_text' );
return (
< div >
< h1 > { heroText || 'Welcome!' } </ h1 >
< button onClick = { () => track ( 'cta_clicked' , { location: 'hero' }) } >
Get Started
</ button >
</ div >
);
}
Server vs Client Components : Grain hooks must be used in Client Components (with 'use client' directive). For Server Components, see the server-side tracking section below.
Track Page Views
Create a client component to track route changes:
// app/analytics-tracker.tsx
'use client' ;
import { useEffect } from 'react' ;
import { usePathname , useSearchParams } from 'next/navigation' ;
import { useTrack } from '@grainql/analytics-web/react' ;
export function AnalyticsTracker () {
const pathname = usePathname ();
const searchParams = useSearchParams ();
const track = useTrack ();
useEffect (() => {
track ( 'page_viewed' , {
page: pathname ,
search: searchParams . toString (),
title: document . title
});
}, [ pathname , searchParams , track ]);
return null ;
}
Add it to your layout:
// app/layout.tsx
import { Providers } from './providers' ;
import { AnalyticsTracker } from './analytics-tracker' ;
export default function RootLayout ({ children } : { children : React . ReactNode }) {
return (
< html lang = "en" >
< body >
< Providers >
< AnalyticsTracker />
{ children }
</ Providers >
</ body >
</ html >
);
}
Pages Router (Next.js 12)
Wrap Your App
// pages/_app.tsx
import type { AppProps } from 'next/app' ;
import { GrainProvider } from '@grainql/analytics-web/react' ;
export default function App ({ Component , pageProps } : AppProps ) {
return (
< GrainProvider
config = { {
tenantId: process . env . NEXT_PUBLIC_GRAIN_TENANT_ID ! ,
defaultConfigurations: {
hero_text: 'Welcome!'
}
} }
>
< Component { ... pageProps } />
</ GrainProvider >
);
}
Add Environment Variable
Create a .env.local file:
NEXT_PUBLIC_GRAIN_TENANT_ID = your-tenant-id
Use in Pages
// pages/index.tsx
import { useTrack , useConfig } from '@grainql/analytics-web/react' ;
export default function HomePage () {
const track = useTrack ();
const { value : heroText } = useConfig ( 'hero_text' );
return (
< div >
< h1 > { heroText || 'Welcome!' } </ h1 >
< button onClick = { () => track ( 'cta_clicked' , { location: 'hero' }) } >
Get Started
</ button >
</ div >
);
}
Track Page Views
Track route changes with Next.js Router:
// pages/_app.tsx
import { useEffect } from 'react' ;
import { useRouter } from 'next/router' ;
import { GrainProvider } from '@grainql/analytics-web/react' ;
import { useTrack } from '@grainql/analytics-web/react' ;
function AnalyticsTracker () {
const router = useRouter ();
const track = useTrack ();
useEffect (() => {
const handleRouteChange = ( url : string ) => {
track ( 'page_viewed' , {
page: url ,
title: document . title
});
};
router . events . on ( 'routeChangeComplete' , handleRouteChange );
// Track initial page
handleRouteChange ( router . pathname );
return () => {
router . events . off ( 'routeChangeComplete' , handleRouteChange );
};
}, [ router , track ]);
return null ;
}
export default function App ({ Component , pageProps } : AppProps ) {
return (
< GrainProvider config = { { tenantId: process . env . NEXT_PUBLIC_GRAIN_TENANT_ID ! } } >
< AnalyticsTracker />
< Component { ... pageProps } />
</ GrainProvider >
);
}
Server-Side Tracking
Track events from API routes or Server Components:
API Routes
// app/api/checkout/route.ts (App Router)
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' ;
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 };
}
Complete Example
Here’s a full Next.js App Router example:
// app/providers.tsx
'use client' ;
import { GrainProvider } from '@grainql/analytics-web/react' ;
export function Providers ({ children } : { children : React . ReactNode }) {
return (
< GrainProvider config = { {
tenantId: process . env . NEXT_PUBLIC_GRAIN_TENANT_ID !
} } >
{ children }
</ GrainProvider >
);
}
// app/layout.tsx
import { Providers } from './providers' ;
export default function RootLayout ({ children } : { children : React . ReactNode }) {
return (
< html lang = "en" >
< body >
< Providers > { children } </ Providers >
</ body >
</ html >
);
}
// app/page.tsx
'use client' ;
import { useTrack , useConfig } from '@grainql/analytics-web/react' ;
export default function HomePage () {
const track = useTrack ();
const { value : heroText } = useConfig ( 'hero_text' );
const { value : ctaText } = useConfig ( 'cta_text' );
return (
< div >
< h1 > { heroText || 'Welcome to Next.js + Grain' } </ h1 >
< button onClick = { () => track ( 'cta_clicked' , { location: 'hero' }) } >
{ ctaText || 'Get Started' }
</ button >
</ div >
);
}
What’s Next?
React Hooks Reference See all available hooks and options
Authentication Secure your analytics with Auth0 or JWT
User Identification Track users across sessions
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.