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.
Shared config
Section titled “Shared config”// lib/gunsole-config.ts — shared configimport 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;Server-side client
Section titled “Server-side client”// 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" }, });}Client-side client
Section titled “Client-side client”// 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;}Server components
Section titled “Server components”// 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 boundary
Section titled “Error boundary”// 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> );}API routes
Section titled “API routes”// pokemon/[name]/route.ts — API route with per-request flushimport { 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 }); }}Both runtimes, one project
Section titled “Both runtimes, one project”Use the same projectId for client and server. In the desktop app, use the runtime tag to distinguish:
runtime: "client"— browser logsruntime: "server"— Node.js logs
All logs from your Next.js app appear under one project, filterable by where they came from.