Overview
Audit log tracking must be done server-side because your API key should never be exposed to the browser. This guide covers patterns for Next.js App Router with Server Actions and middleware.
Never use your imk_ API key in client-side code, use client components, or any code that runs in the browser. API keys in client-side bundles are visible to anyone inspecting your page source.
Setup
Install the SDK:
npm install @getimmutable/sdk
Add your credentials to .env.local:
GETIMMUTABLE_API_KEY=imk_your_api_key_here
GETIMMUTABLE_BASE_URL=https://getimmutable.dev
Create a shared client instance:
// lib/immutable.ts
import { ImmutableClient } from "@getimmutable/sdk";
export const immutable = new ImmutableClient({
apiKey: process.env.GETIMMUTABLE_API_KEY!,
baseUrl: process.env.GETIMMUTABLE_BASE_URL!,
});
Server Actions
Track user actions in Server Actions:
// app/documents/actions.ts
"use server";
import { immutable } from "@/lib/immutable";
import { PendingEvent } from "@getimmutable/sdk";
import { auth } from "@/lib/auth";
export async function createDocument(formData: FormData) {
const session = await auth();
if (!session?.user) throw new Error("Unauthorized");
const title = formData.get("title") as string;
// Create the document in your database
const document = await db.document.create({ data: { title } });
// Track the action
await new PendingEvent(immutable, {
id: session.user.id,
name: session.user.name,
type: "user",
})
.session(session.sessionId)
.track("document.created", "document", {
resource_id: document.id,
resource_name: title,
});
return document;
}
API Route Handlers
Track actions in API route handlers:
// app/api/documents/[id]/route.ts
import { immutable } from "@/lib/immutable";
import { NextRequest, NextResponse } from "next/server";
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
await db.document.delete({ where: { id: params.id } });
await immutable.track({
actor_id: session.user.id,
actor_name: session.user.name,
action: "document.deleted",
resource_id: params.id,
resource: "document",
session_id: session.sessionId,
});
return NextResponse.json({ success: true });
}
Middleware for Session Tracking
Use Next.js middleware to generate consistent session IDs:
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { v4 as uuidv4 } from "uuid";
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Generate session ID if not present
if (!request.cookies.get("audit_session_id")) {
const sessionId = `sess_${uuidv4()}`;
response.cookies.set("audit_session_id", sessionId, {
httpOnly: true,
secure: true,
sameSite: "lax",
maxAge: 60 * 60 * 24, // 24 hours
});
}
return response;
}
Then read the session ID in your Server Actions:
import { cookies } from "next/headers";
const sessionId = (await cookies()).get("audit_session_id")?.value;
Embeddable Viewer
Generate a viewer token server-side and pass it to a client component:
// app/audit-log/page.tsx
import { immutable } from "@/lib/immutable";
import { AuditLogViewer } from "./viewer";
export default async function AuditLogPage() {
const { token, expires_at } = await immutable.createViewerToken({
tenantId: "org_7rT2xBc",
ttl: 3600,
});
return <AuditLogViewer token={token} />;
}
// app/audit-log/viewer.tsx
"use client";
export function AuditLogViewer({ token }: { token: string }) {
return (
<iframe
src={`https://getimmutable.dev/viewer?token=${token}`}
className="w-full h-[600px] border-0 rounded-lg"
/>
);
}