> ## 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.

# Next.js

> Install Grain Tag in the App Router or the Pages Router and send server-side events with Analytics Web.

This page installs Grain Tag in a Next.js app, with the App Router (Next.js 13 and later) or the
Pages Router (Next.js 12 and earlier). Then it sends an event from a route handler with Analytics
Web. Steps 1 to 5 use `@grainql/tag`. Step 6 uses `@grainql/analytics-web`.

## Before you start

You need three things:

* Your tenant alias. The dashboard at `https://grainql.com/dashboard` shows it. It is not the
  UUID.
* A `.env.local` file. Next.js exposes every variable with the `NEXT_PUBLIC_` prefix to the
  browser. A variable without the prefix stays on the server.
* For step 6, a secret key from the dashboard, in the server-only variable `GRAIN_SECRET_KEY`.
  Read [Authentication](/authentication).

```bash theme={null}
NEXT_PUBLIC_GRAIN_TENANT_ID=your-tenant-id
GRAIN_TENANT_ID=your-tenant-id
```

## 1. Install Grain Tag

```bash theme={null}
npm install @grainql/tag
```

## 2. Initialize in the App Router

Grain Tag runs in the browser. Create a Client Component that calls `init` one time.

```tsx theme={null}
// 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 the component to the root layout.

```tsx theme={null}
// app/layout.tsx
import { GrainAnalytics } from './grain';

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

`init` returns a no-op stub outside a browser, so the import is safe in a server render. If you
use the Pages Router, skip to step 3.

## 3. Initialize in the Pages Router

Call `init` in a `useEffect` in `pages/_app.tsx`.

```tsx theme={null}
// 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} />;
}
```

## 4. Make sure that events arrive

Open the app in a browser. In the developer console, run:

```javascript theme={null}
GrainTag.track('installation_test');
```

The dashboard shows the `installation_test` event within 30 seconds. If the event does not
appear, read [Troubleshooting](/troubleshooting).

## 5. Track an event and identify a user

Call `track` and `identify` from a Client Component, or inside a `useEffect`. In the App Router,
the component needs the `'use client'` directive. Both routers navigate through the History
API, so Grain Tag tracks each route change as a page view.

```tsx theme={null}
// app/page.tsx
'use client';

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

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

  return (
    <div>
      <button onClick={() => track('cta_clicked', { location: 'hero' })}>Get Started</button>
      <form onSubmit={handleLogin}>...</form>
    </div>
  );
}
```

`track` and `identify` called before `init` are queued and replayed after initialization. For
consent, call `getInstance()?.consent.grant()` from a Client Component. Read
[Track an event](/core/event-tracking) and [Identify users](/core/user-identification).

## 6. Server-side events

Route handlers and Server Actions use Analytics Web, a different package, with the
`SERVER_SIDE` strategy and the secret key.

```bash theme={null}
npm install @grainql/analytics-web
```

```typescript theme={null}
// app/api/checkout/route.ts
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();

  await grain.track('checkout_completed', {
    order_id: body.orderId,
    total: body.total
  }, { flush: true });

  return Response.json({ success: true });
}
```

The `{ flush: true }` option sends the event before the function returns. Without it, a
serverless function can stop before the batch timer fires, and the event is lost. A Server
Action in `app/actions.ts` with the `'use server'` directive uses the same client and the same
`track` call.

<Warning>
  Do not put the secret key in a `NEXT_PUBLIC_` variable. Next.js sends every `NEXT_PUBLIC_`
  variable to the browser.
</Warning>

Read [Node.js and servers](/quickstart/nodejs).

## Next steps

<CardGroup cols={2}>
  <Card title="Core methods" icon="book" href="/api-reference/core-methods">
    Every method of both SDKs, with signatures.
  </Card>

  <Card title="Identify users" icon="user" href="/core/user-identification">
    Anonymous IDs, daily rotating IDs, and `identify`.
  </Card>

  <Card title="React hooks" icon="react" href="/react/overview">
    Analytics Web, the provider, and remote configuration in React.
  </Card>

  <Card title="Configuration options" icon="gear" href="/advanced/configuration">
    Every option of both SDKs, with defaults.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.