How to Add Audit Logs to a Next.js App in 10 Minutes
If you are building a B2B SaaS on Next.js and an enterprise prospect just asked for SOC 2 evidence, you need audit logs in your application — not next quarter, this week. This guide shows how to add tamper-evident, multi-tenant audit logs to a Next.js App Router application using the AuditKit SDK. Total setup time is under 10 minutes; the cryptographic chain integrity is built in.
What Audit Events Should a SaaS Application Log?
SOC 2 auditors care about a specific subset of application events. The pattern is consistent across every B2B SaaS: capture business events that change permissions, access, billing, or data ownership. Concretely, a Next.js B2B app should log:
- Authentication events (sign in, sign out, failed login, MFA challenge, password reset).
- Organization and team events (create org, invite member, accept invite, change role, remove member).
- Permission changes (grant role, revoke role, change permission scope).
- Data access on sensitive resources (read of customer data, export of records, API key usage).
- Configuration changes (security settings, integration tokens, billing changes, plan upgrades).
What to skip: routine page views, navigation events, idempotent reads of public data, or anything that does not have audit value. Log fewer, better events. A wide audit log is harder to search and harder for an auditor to read; a focused audit log is more credible and more useful.
How Do I Install the AuditKit SDK in Next.js?
AuditKit publishes a TypeScript SDK that works in both the Node.js runtime and the Edge runtime that Next.js middleware uses.
npm install @auditkit/sdk
# or
pnpm add @auditkit/sdk
# or
yarn add @auditkit/sdk
Add the AuditKit API key to your .env.local (or your hosting platform's secret store):
AUDITKIT_API_KEY=sk_your_key_here
AUDITKIT_BASE_URL=https://api.auditkit.dev
Self-hosters point AUDITKIT_BASE_URL at their own deployment. Cloud users use the default
URL. The SDK reads both environment variables automatically.
Where Should the AuditKit Client Live?
Create a single shared client instance in src/lib/auditkit.ts (or wherever you keep
infrastructure modules). This avoids reinitializing the SDK on every request.
// src/lib/auditkit.ts
import { AuditKit } from '@auditkit/sdk';
export const auditLog = new AuditKit({
apiKey: process.env.AUDITKIT_API_KEY!,
baseURL: process.env.AUDITKIT_BASE_URL,
});
Import auditLog from this module anywhere you need to record an event. The client is
thread-safe and reuses HTTP connections across calls.
How Do I Log an Event from a Server Action?
Next.js Server Actions are the natural place to log audit events because they always run on the
server with access to the authenticated session. Add a single auditLog.event call after
the business operation completes.
// src/app/(dashboard)/team/invite-member-action.ts
'use server';
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
import { auditLog } from '@/lib/auditkit';
export async function inviteMember(formData: FormData) {
const session = await auth();
if (!session) throw new Error('Unauthorized');
const email = formData.get('email') as string;
const role = formData.get('role') as string;
const invite = await db.invite.create({
data: {
email,
role,
orgId: session.orgId,
invitedBy: session.userId,
},
});
await auditLog.event({
actor: session.userId,
action: 'org.member.invite',
resource: invite.id,
tenantId: session.orgId,
metadata: {
inviteEmail: email,
role,
ipAddress: session.ipAddress,
},
});
return invite;
}
The pattern is consistent: actor (who did it), action (what they did, in dotted-namespace form), resource (what they did it to), tenantId (which customer's data this is — critical for multi-tenant SaaS), and metadata for anything else an auditor might care about.
How Do I Log Events from API Routes?
Same pattern in App Router API routes (route.ts handlers):
// src/app/api/keys/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
import { auditLog } from '@/lib/auditkit';
export async function POST(req: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const { name } = await req.json();
const apiKey = await db.apiKey.create({
data: { name, orgId: session.orgId, createdBy: session.userId },
});
await auditLog.event({
actor: session.userId,
action: 'apikey.create',
resource: apiKey.id,
tenantId: session.orgId,
metadata: { keyName: name },
});
return NextResponse.json({ id: apiKey.id });
}
Should I Log from Middleware?
Sparingly. Next.js middleware runs on the Edge runtime and is invoked on every matching request, which means logging there can produce a lot of noise. Use middleware logging only for high-signal security events: failed authentication attempts, blocked IP addresses, and rate-limit violations.
// src/middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { auditLog } from '@/lib/auditkit';
export async function middleware(req: NextRequest) {
const sessionToken = req.cookies.get('session')?.value;
if (!sessionToken && req.nextUrl.pathname.startsWith('/dashboard')) {
await auditLog.event({
actor: 'anonymous',
action: 'auth.unauthorized_access',
resource: req.nextUrl.pathname,
tenantId: 'system',
metadata: {
ipAddress: req.ip,
userAgent: req.headers.get('user-agent'),
referer: req.headers.get('referer'),
},
});
return NextResponse.redirect(new URL('/sign-in', req.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
};
For everyday business events, log from Server Actions or API routes — never from middleware. Middleware is for security-event logging only.
How Do I Show Each Tenant Their Own Audit Trail?
The most-requested enterprise feature is "let our customers see their own audit trail." With AuditKit's tenant-scoped events, this is a single API call:
// src/app/(dashboard)/audit/page.tsx
import { auth } from '@/lib/auth';
import { auditLog } from '@/lib/auditkit';
export default async function AuditPage() {
const session = await auth();
if (!session) return null;
const events = await auditLog.list({
tenantId: session.orgId,
limit: 100,
order: 'desc',
});
return (
<div>
<h1>Audit Trail</h1>
<ul>
{events.map((e) => (
<li key={e.id}>
<span>{e.occurredAt}</span>
<span>{e.actor}</span>
<span>{e.action}</span>
<span>{e.resource}</span>
</li>
))}
</ul>
</div>
);
}
Because every event is tenant-scoped, the customer's view is automatically isolated. Customer A never sees Customer B's events. SOC 2 auditors specifically look for this isolation in the application code.
How Do I Export Evidence for an Auditor?
At audit time, your auditor wants a tenant-scoped, time-bounded export. AuditKit's evidence export endpoint produces it directly:
// scripts/export-evidence.ts
import { auditLog } from '@/lib/auditkit';
const evidence = await auditLog.exportEvidence({
tenantId: 'org_acme_corp',
startDate: '2025-11-01',
endDate: '2026-04-30',
format: 'csv', // or 'json' or 'jsonl'
includeChainProof: true,
});
await fs.writeFile('acme-corp-audit-evidence.csv', evidence);
With includeChainProof: true, the export includes the cryptographic hash chain proof so
the auditor can independently verify that no events were tampered with during the observation window.
This is the moment that makes auditor reviews fast — clean, verifiable evidence in a format the auditor
can read directly.
What About Performance? Will Audit Logging Slow Down My App?
The AuditKit SDK batches and dispatches events asynchronously by default. auditLog.event
returns immediately; the network round-trip happens in the background. Typical overhead is under 0.5ms
on the request path. For high-throughput operations (e.g., bulk imports), the SDK supports explicit
batching:
const batch = auditLog.batch();
for (const record of records) {
batch.event({
actor: session.userId,
action: 'data.import',
resource: record.id,
tenantId: session.orgId,
});
}
await batch.commit();
The batch sends a single network request with all events, hash-chained server-side in order. For a 1,000-record import, batched logging is roughly 50x faster than individual events.
What Should I Do Next?
The 10-minute version above gets you a working tenant-scoped audit log. Beyond that, the natural next steps are:
- Audit your existing routes and Server Actions and add events for the business operations identified at the top of this post.
- Add the audit-trail viewer page to your dashboard so enterprise customers can self-serve their compliance evidence.
- Configure SIEM integration if you have a Splunk, Datadog, or Elastic instance — events flow there in real time.
- Run an evidence export for the previous 30 days as a smoke test, and inspect the chain proof to confirm integrity.
- Document your event taxonomy (the list of actions you log) so your team uses consistent action names going forward.
Key Takeaways
- Add the AuditKit SDK in 4 lines: install, configure env vars, create the client, log events.
- Log from Server Actions and API routes for business events. Use middleware only for high-signal security events.
- Always include
tenantId. Multi-tenant scoping is what makes the audit log enterprise-ready. - Use
auditLog.batch()for bulk operations to avoid 1:1 network overhead per event. - Evidence export with chain proof gives auditors the cryptographically verifiable trail they want — and removes the week-before-the-audit scramble.
- Start with a focused event taxonomy and resist the urge to log everything; an audit log is more useful when it captures the events that matter and excludes the noise.
Ready to ship audit logging?
AuditKit gives you tamper-evident audit trails and SOC 2 evidence collection in one platform. Start free, or skip the trial below.
Related Articles
How to Add Audit Logs to an Express.js Application (2026 Guide)
Step-by-step guide to adding tamper-evident, multi-tenant audit logs to an Express.js or Node.js API using the AuditKit SDK. Covers middleware patterns, route-level instrumentation, async batching, and tenant-scoped evidence export.
Read moreCompliance Frameworks for B2B SaaS in 2026: SOC 2 vs ISO 27001 vs HIPAA vs GDPR vs PCI DSS vs FedRAMP — Side-By-Side
Every modern B2B SaaS eventually needs multiple compliance attestations. This guide compares 11 frameworks (SOC 2, ISO 27001, HIPAA, GDPR, PCI DSS, FedRAMP, CMMC, DORA, NIS2, SOX, EU AI Act) on scope, audit log requirements, retention, and overlap so you can plan the right multi-framework strategy.
Read moreSOC 2 vs ISO 27001: Which to Pursue First in 2026
SOC 2 vs ISO 27001 head-to-head: scope, cost, auditor pool, sales acceleration, and the right order for a B2B SaaS pursuing both. The wrong-order decision costs 4-6 months of redundant work.
Read more