FRMWRK
Next.js Boilerplate
Build tools with AI

Security

Four hard rules applied to every API route and page. These are non-negotiable — the AI follows them automatically when generating code.

1

Validate all input with Zod

Every API route that accepts a request body must validate it with Zod before use. Never trust request.json() directly.

✓ correct

import { z } from 'zod'

const schema = z.object({
  email: z.string().email(),
  message: z.string().min(1).max(1000),
})

export async function POST(req: Request) {
  const body = await req.json()
  const parsed = schema.safeParse(body)
  if (!parsed.success) {
    return Response.json({ error: parsed.error.flatten() }, { status: 400 })
  }
  // safe to use parsed.data
}

✗ avoid

export async function POST(req: Request) {
  const { email, message } = await req.json() // ❌ never do this
}
2

Check session on protected routes

Any route or page that requires login must check the session at the top and return 401 immediately if there is none. Do not rely on middleware alone.

✓ correct

import { auth } from '@/lib/auth'
import { headers } from 'next/headers'

export async function GET() {
  const session = await auth?.api.getSession({ headers: await headers() })
  if (!session) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }
  // session.user is now available
}

✗ avoid

export async function GET() {
  // ❌ assumes middleware handles auth — it doesn't always fire
  const data = await getSecretData()
  return Response.json(data)
}
3

Never put secrets in source code

API keys, tokens, and passwords go in .env only. Reference them via process.env. Never hardcode them, log them, or expose them in client-side code.

✓ correct

// .env
EXTERNAL_API_KEY=sk-...

// lib/integrations/myservice.ts
const API_KEY = process.env.EXTERNAL_API_KEY ?? ''

✗ avoid

// ❌ never hardcode secrets
const API_KEY = 'sk-abc123...'

// ❌ never expose in client components (no 'use client' + no NEXT_PUBLIC_ for secrets)
const API_KEY = process.env.NEXT_PUBLIC_SECRET_KEY
4

Use Zod on all external data

Data from external APIs, webhooks, and form submissions must be validated before use — not just request bodies.

✓ correct

import { z } from 'zod'

const PersonioEmployeeSchema = z.object({
  id: z.number(),
  attributes: z.object({
    first_name: z.object({ value: z.string() }),
    email: z.object({ value: z.string().email() }),
  }),
})

const res = await personioFetch('/company/employees')
const raw = await res.json()
const employee = PersonioEmployeeSchema.parse(raw.data[0])

✗ avoid

const res = await personioFetch('/company/employees')
const { data } = await res.json()
const email = data[0].attributes.email.value // ❌ no validation