# Next.js

> Add Centinel Analytica to your Next.js application.

Source: https://docs.centinelanalytica.com/platforms/application/nextjs

## Overview

Centinel validates each request before it reaches your application. Suspicious requests are blocked or redirected to an interstitial page. The client script helps distinguish legitimate users from bots.

Blocked requests are redirected to `/block` and challenges to `/interstitial`. Create both pages in your app. Neither path is configurable in the current release.

## Prerequisites

* Site key (public, used by the browser script)
* Secret key (server-only, used for validation)
* Next.js 13 or later with the App Router, React 18+, and Node.js 18+
* A `/block` page and an `/interstitial` page in your app. The middleware sends visitors to these paths, and neither path is configurable.

## Install

In your Next.js project:

```bash
npm install @centinel/nextjs
```

## Configure

1. **Environment variables**

   Add your Centinel keys to `.env`:

   ```bash
   CENTINEL_SITE_KEY=YOUR_SITE_KEY
   CENTINEL_SECRET_KEY=YOUR_SECRET_KEY
   NEXT_PUBLIC_CENTINEL_SITE_KEY=YOUR_SITE_KEY
   ```

   > **Key safety:** Use `CENTINEL_SECRET_KEY` server-side only. Don't expose it to the browser.

   > **Which site key to use:** Use `CENTINEL_SITE_KEY` for server-side code (layouts, middleware). Use{' '}
   > `NEXT_PUBLIC_CENTINEL_SITE_KEY` if using `CentinelLayout` in client-side pages.

2. **Client script**

   Add CentinelLayout to your root layout:

   ```typescript
   // app/layout.tsx (server-side)
   import { CentinelLayout } from '@centinel/nextjs';

   export default function RootLayout({
     children,
   }: {
     children: React.ReactNode;
   }) {
     return (
       <html>
         <body>
           <CentinelLayout siteKey={process.env.CENTINEL_SITE_KEY!}>
             {children}
           </CentinelLayout>
         </body>
       </html>
     );
   }
   ```

   Or if using in a client-side page:

   ```typescript
   // app/page.tsx (client-side)
   'use client';
   import { CentinelLayout } from '@centinel/nextjs';

   export default function HomePage(): React.JSX.Element {
     return (
       <CentinelLayout siteKey={process.env.NEXT_PUBLIC_CENTINEL_SITE_KEY!}>
         {/* Your page content */}
       </CentinelLayout>
     );
   }
   ```

3. **Protection**

   Choose between automatic middleware or manual validation:

   **Middleware (recommended)**

   Create `middleware.ts` in your project root. On Next.js 16 the file is `proxy.ts` and the export is `proxy` instead; run `npx @next/codemod@canary middleware-to-proxy` to convert.

   ```typescript
   import type { NextRequest } from 'next/server';
   import { createCentinelMiddlewareFromEnv } from '@centinel/nextjs';

   const centinel = createCentinelMiddlewareFromEnv();

   export default async function middleware(request: NextRequest) {
     const result = await centinel(request);
     return result.response;
   }

   export const config = {
     matcher: ['/api/:path*', '/dashboard/:path*']
   };
   ```

   > **Return result.response, not the handler:** `createCentinelMiddlewareFromEnv()` returns a function resolving to a `CentinelResult` object, not a `Response`. Exporting it directly makes Next.js reject the return value and every matched request fails with a 500.

   > **Next.js 16 ignores middleware.ts:** Next.js 16 replaced the `middleware` convention with `proxy.ts`. A leftover `middleware.ts` is skipped at build time without an error, so protection silently stops running after the upgrade.

   **Manual validation**

   Use in specific API routes when middleware doesn't fit:

   ```typescript
   // app/api/login/route.ts
   import { createRequestValidatorFromEnv } from '@centinel/nextjs';
   import { NextRequest, NextResponse } from 'next/server';

   const { isBot } = createRequestValidatorFromEnv();

   export async function POST(request: NextRequest) {
     if (await isBot(request)) {
       return NextResponse.json({ error: 'Blocked' }, { status: 403 });
     }
     return handleLogin(request);
   }
   ```

   Or with custom configuration:

   ```typescript
   // app/api/protected/route.ts
   import { createRequestValidator } from '@centinel/nextjs';
   import { NextRequest, NextResponse } from 'next/server';

   const { isBot } = createRequestValidator({
     siteKey: 'YOUR_SITE_KEY',
     secretKey: 'YOUR_SECRET_KEY'
   });

   export async function POST(request: NextRequest) {
     if (await isBot(request)) {
       return NextResponse.json({ error: 'Access denied' }, { status: 403 });
     }
     
     // Your protected logic
   }
   ```

### Custom configuration

Pass config directly instead of using environment variables:

```typescript
import type { NextRequest } from 'next/server';
import { createCentinelMiddleware } from '@centinel/nextjs';

const centinel = createCentinelMiddleware({
  siteKey: 'YOUR_SITE_KEY',
  secretKey: 'YOUR_SECRET_KEY',
  timeout: 500,
});

export default async function middleware(request: NextRequest) {
  return (await centinel(request)).response;
}
```

The validator call times out after 500ms by default and fails open. After repeated failures the
middleware stops calling the validator at all, backing off from one second up to five minutes, so a
sustained outage leaves traffic unvalidated for that window.

### Route matching

Specify which routes to protect:

```typescript
export const config = {
  matcher: [
    '/api/:path*',        // All API routes
    '/dashboard/:path*',  // Dashboard pages
    '/admin/:path*'       // Admin area
  ]
};
```

## Verify

Run the dev server with debug output on and load a protected route:

```bash
DEBUG=centinel:* next dev
```

Look for `Validation completed in Nms`. Then confirm in your browser's dev tools that a `_centinel`
cookie is set and that `script.js` is requested from the collector host.

If every request is allowed, check the secret key first: a 4xx from the validator logs
`Validator service responded with 401` and then fails open. If requests hang or 500 instead, confirm
that `/block` and `/interstitial` both exist, and on Next.js 16 that your file is named `proxy.ts`.

## Changelog

* **v1.2.2** — Response header passthrough
