Skip to content

Next.js Integration

Next.js runs code on both the client and server. Use @gunsole/web for client components and @gunsole/core for server components, API routes, and middleware.

// lib/gunsole-config.ts — shared config
import type { GunsoleClientConfig } from "@gunsole/core";
export const gunsoleConfig = {
projectId: "my-nextjs-app",
mode: "local",
env: "development",
appName: "Next.js App",
appVersion: "1.0.0",
defaultTags: { framework: "nextjs" },
} satisfies GunsoleClientConfig;
// lib/gunsole-server.ts — server-side (reads session from cookie)
import { type GunsoleClient, createGunsoleClient } from "@gunsole/core";
import { cookies } from "next/headers";
import { gunsoleConfig } from "./gunsole-config";
const COOKIE_NAME = "gunsole_session";
export async function getServerGunsole(): Promise<GunsoleClient> {
const cookieStore = await cookies();
const sessionId = cookieStore.get(COOKIE_NAME)?.value;
return createGunsoleClient({
...gunsoleConfig,
sessionId,
defaultTags: { ...gunsoleConfig.defaultTags, runtime: "server" },
});
}
// lib/gunsole-client.ts — browser-side singleton with session persistence
"use client";
import {
type GunsoleClient,
createGunsoleClient,
persistSession,
} from "@gunsole/web";
import { gunsoleConfig } from "./gunsole-config";
let gunsole: GunsoleClient | null = null;
export function getClientGunsole(): GunsoleClient {
if (!gunsole) {
gunsole = createGunsoleClient({
...gunsoleConfig,
defaultTags: { ...gunsoleConfig.defaultTags, runtime: "client" },
});
persistSession(gunsole);
}
return gunsole;
}
// page.tsx — server component logging (must flush explicitly!)
export default async function Home() {
const gunsole = await getServerGunsole();
gunsole.info({
bucket: "pages",
message: "Home page rendered (server)",
context: { timestamp: Date.now() },
});
await gunsole.flush(); // Important: flush in server components
return <GunsoleTest />;
}
// error.tsx — Next.js error boundary
"use client";
import { getClientGunsole } from "@/lib/gunsole-client";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
const gunsole = getClientGunsole();
useEffect(() => {
gunsole.fatal({
bucket: "fatal",
message: error.message,
context: {
name: error.name,
stack: error.stack,
digest: (error as any).digest,
},
});
}, [error]);
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
);
}
// pokemon/[name]/route.ts — API route with per-request flush
import { getServerGunsole } from "@/lib/gunsole-server";
export async function GET(request: Request, { params }: { params: Promise<{ name: string }> }) {
const gunsole = await getServerGunsole();
const { name } = await params;
gunsole.info({
bucket: "api",
message: `API request: GET /api/pokemon/${name}`,
tags: { endpoint: "pokemon", method: "GET" },
});
try {
const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`);
if (!response.ok) throw new Error(`Pokemon not found: ${name}`);
const data = await response.json();
gunsole.info({
bucket: "api",
message: `Pokemon fetched: ${data.name}`,
context: { pokemonId: data.id },
tags: { endpoint: "pokemon", status: "200" },
});
await gunsole.flush();
return Response.json(data);
} catch (err) {
gunsole.error({
bucket: "api",
message: `Failed to fetch Pokemon: ${name}`,
context: { error: err instanceof Error ? err.message : "Unknown" },
tags: { endpoint: "pokemon", status: "error" },
});
await gunsole.flush();
return Response.json({ error: "Not found" }, { status: 404 });
}
}

Use the same projectId for client and server. In the desktop app, use the runtime tag to distinguish:

  • runtime: "client" — browser logs
  • runtime: "server" — Node.js logs

All logs from your Next.js app appear under one project, filterable by where they came from.