# Next.js

> Identify visitors in Next.js 15 and newer. A provider that works from a Server Component layout, a hidden form field, and a Server Action helper that reads it.

Last updated: 2026-09-17

`@fingerly/next` builds on [`@fingerly/react`](https://docs.fingerly.io/docs/sdks/react) for the App Router and the Pages Router. It adds a provider you can render from a Server Component, a form field that carries the request ID, and a server entry point with helpers that never import React.

## Requirements

- Next.js 15 or newer, with React 19.
- A [public key](https://docs.fingerly.io/docs/api-keys) with your site in its allowed origins.

## Install

```bash npm
npm install @fingerly/next
```

```bash pnpm
pnpm add @fingerly/next
```

```bash yarn
yarn add @fingerly/next
```

## Add the provider

Read the key in your root layout, which stays a Server Component, and pass it to the provider.

```tsx app/layout.tsx
import { FingerlyProvider } from '@fingerly/next'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <FingerlyProvider apiKey={process.env.FINGERLY_PUBLIC_KEY}>{children}</FingerlyProvider>
      </body>
    </html>
  )
}
```

The provider also reads `NEXT_PUBLIC_FINGERLY_API_KEY`, `NEXT_PUBLIC_FINGERLY_ENDPOINT` and `NEXT_PUBLIC_FINGERLY_ENDPOINTS` (a JSON array) when a prop is not given. Passing the key as a prop is the arrangement that always works. With the Pages Router, render the same provider in `_app.tsx`.

## Carry the request ID through a Server Action

`FingerlyRequestId` renders a hidden input that fills itself once the identification finishes. `readRequestId` reads it back on the server.

```tsx app/signup/page.tsx
import { FingerlyRequestId } from '@fingerly/next'
import { signUp } from './actions'

export default function Page() {
  return (
    <form action={signUp}>
      <input name="email" type="email" />
      <FingerlyRequestId />
      <button>Create account</button>
    </form>
  )
}
```

```ts app/signup/actions.ts
'use server'
import { readRequestId } from '@fingerly/next/server'
import { load } from '@fingerly/node'

const fingerly = load({ secretKey: process.env.FINGERLY_SECRET_KEY! })

export async function signUp(formData: FormData) {
  const requestId = readRequestId(formData)   // null when the form carried none
  if (!requestId) return { error: 'unverified' }

  const event = await fingerly.events.get(requestId)
  if (event.suspect_level === 'high') return { error: 'review' }
  // create the account
}
```

- The field is named `fingerly_request_id` (`REQUEST_ID_FIELD`). Pass `name` to change it.
- `readRequestId` returns `null` for a missing, empty or malformed value and never throws.
- Several fields on one page still make one identification.

## Hooks

`useIdentify`, `useVerdict` and `useFingerly` are re-exported from `@fingerly/react` and behave identically. See the [React SDK](https://docs.fingerly.io/docs/sdks/react#useidentify).

```tsx app/checkout/Checkout.tsx
'use client'
import { useIdentify } from '@fingerly/next'

export function Checkout() {
  const { identify, isLoading } = useIdentify()
  // …
}
```

## Entry points

| Import | Runs in | Contains |
| --- | --- | --- |
| `@fingerly/next` | The browser | The provider, `FingerlyRequestId` and the hooks. A `'use client'` module. |
| `@fingerly/next/server` | The server | `readRequestId`, `REQUEST_ID_FIELD` and `createFingerlyProxy`. No React. |

> **Warning:** Import server helpers from `@fingerly/next/server` only. Importing them from the main entry inside a Server Action gives a client reference that fails at runtime.

## Proxy through a route handler

`createFingerlyProxy` is the [Node.js SDK's](https://docs.fingerly.io/docs/sdks/node#serve-the-browser-sdk-from-your-domain) `createProxy`, re-exported. Mount it on a catch-all route and point the provider at it.

```ts app/metrics/[...path]/route.ts
import { createFingerlyProxy } from '@fingerly/next/server'

export const POST = createFingerlyProxy({
  proxyKey: process.env.FINGERLY_PROXY_KEY!,
  prefix: '/metrics',
  clientIp: (request) => request.headers.get('x-real-ip') ?? '',
})
```

```tsx app/layout.tsx
<FingerlyProvider apiKey={process.env.FINGERLY_PUBLIC_KEY} endpoints="/metrics">
```

Resolve the client address only from a header your hosting platform sets. See [proxy integrations](https://docs.fingerly.io/docs/proxy-integrations).
