> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heretic.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js

> Verify a widget form in a Next.js App Router server route.

Use this complete `/check` route to run the widget and save one receipt after server verification. It uses Node 24+ and persistent SQLite storage. The [server client](/integration/server-clients) can also be used with your application's existing database.

## Install the example

[Download the server examples](https://heretic.tech/downloads/heretic-server-examples.zip) and put the extracted `server-examples` directory beside your app's `app` directory. Add `app/check/route.js`:

```js theme={"dark"}
import { fromEnvironment } from '../../server-examples/examples/lib/form-flow.mjs';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
let flow;
const handle = request => (flow ??= fromEnvironment()).handle(request);
export const GET = handle;
export const POST = handle;
```

This [route handler](https://nextjs.org/docs/app/getting-started/route-handlers) serves HTML on GET and verifies the form on POST. Keep `/check` free of a `page.js`; the handler owns that route. If your app uses `src/app`, put `server-examples` beside `src/app` instead.

Copy the archive's `.env.example` values into your app's `.env.local`. Run your app's usual development command and open `http://127.0.0.1:3000/check`. Keep the secret in server-only environment variables; do not use a `NEXT_PUBLIC_` prefix.

The initial values use test keys and save a local receipt without real measurements. Follow the [production key setup](/integration/plain-html#2-use-your-keys) to run real checks on your verified domain.

## Bind verification to the action

The shared handler sets an HttpOnly SameSite cookie and saves an attempt before rendering `data-cdata`. It verifies the token against that attempt's hostname, site, action and ID. Server policy is `uncontradicted`; change it together with the widget presentation if your action requires a [different policy](/api/siteverify-endpoint#request).

Verification failures return 403. Missing proof returns 400. An unavailable API returns 503 and leaves the action pending. Retries use the same token and idempotency key; a completed attempt returns the existing receipt.

Replace the receipt write in `examples/lib/form-flow.mjs` with your business write in the same transaction. Bind your authenticated user and the operation's data to the pending record. The example's random cookie identifies a browser session, not an account.

## Deployment

Keep the Node runtime. SQLite needs a persistent writable disk; a temporary serverless filesystem cannot preserve the single-use guarantee. On serverless or multiple hosts, keep the client and move the attempt/write transaction into your shared database.

The handler sends `Cache-Control: no-store` and a CSP that allows Heretic scripts and frames. Merge the CSP sources into your app's policy if you move the form into an existing page. The widget [script API](/integration/challenge-widget#script-api) supports explicit rendering for client-side navigation.


## Related topics

- [Node, Python and PHP](/integration/server-clients.md)
- [Quickstart](/quickstart.md)
- [Script tag](/collector/script-tag.md)
- [npm package](/collector/npm-package.md)
- [Create a challenge](/api/challenge-mint.md)
