checkrd

Next.js

Add Checkrd policy enforcement to Next.js Route Handlers and Server Actions.

Next.js

Checkrd ships first-class Next.js helpers for both the App Router (Route Handlers) and Server Actions, with the right initialization shape for the Edge Runtime and Node runtime simultaneously.

Install

bash
npm install checkrd

Initialize once

Next.js bundles each route file separately. Initialize Checkrd at module scope so the WASM engine is loaded once per worker:

typescript
// app/lib/checkrd.ts
import { initCheckrd } from "checkrd/integrations/next";

export const checkrd = initCheckrd({
  policy: "/var/run/secrets/policy.yaml",
  agentId: "web",
});

initCheckrd is idempotent across hot reloads and works in both Node and Edge runtimes (it uses initAsync under the hood). Tests can call resetCheckrdNext() between cases.

Route Handlers (App Router)

typescript
// app/api/chat/route.ts
import { checkrdRoute } from "checkrd/integrations/next";
import { checkrd } from "@/lib/checkrd";

export const POST = checkrdRoute(checkrd, async (req) => {
  const { messages } = await req.json();
  // ... your handler ...
  return Response.json({ result: "..." });
});

checkrdRoute wraps the handler so every inbound request is policy-evaluated before the body runs. On deny, returns a 403 with the error envelope:

json
{
  "error": {
    "type": "policy_denied",
    "message": "deny rule 'block-pii' fired",
    "request_id": "...",
    "dashboard_url": "https://app.checkrd.io/events/..."
  }
}

Server Actions

typescript
// app/actions.ts
"use server";
import { checkrdAction } from "checkrd/integrations/next";
import { checkrd } from "@/lib/checkrd";

export const summarize = checkrdAction(checkrd, async (input: string) => {
  return { summary: input.slice(0, 100) };
});

checkrdAction wraps a Server Action so the policy is evaluated against the action call before the function body runs.

What gets enforced

HelperSynthetic URLBody
checkrdRoute{request.method} {request.url}The request body (JSON or raw)
checkrdActionnext-action://{actionName}The action arguments

You write rules against these the same as any other Checkrd rule.

yaml
agent: web
default: deny

rules:
  - name: allow-public-chat
    allow:
      url: "POST */api/chat"

  - name: allow-summarize-action
    allow:
      url: "next-action://summarize"

Pages Router (legacy)

The same helpers work for Pages Router API routes — wrap your default export with checkrdRoute. Recommended only for existing Pages Router apps; new projects should use App Router.

Edge Runtime

Both helpers detect the runtime automatically. In Edge, initCheckrd loads the WASM via fetch + WebAssembly.compile. No node:* imports happen at module load — the bundle is edge-clean. The tests/edge_runtime.test.ts regression test in the repo locks this in.

Caveats

  • initCheckrd is module-scoped. Don't call it inside the handler body — the engine should be reused across requests for sub-millisecond evaluation.
  • Environment variables. In Edge Runtime, only process.env.CHECKRD_API_KEY and friends declared in next.config.ts are visible. Pass them explicitly when needed.
  • Server Actions arguments are not always JSON-serializable. The adapter uses JSON.stringify with a fallback to String(value); complex objects (Dates, Buffers, FormData) lose detail in body matchers.