Client Logging
Client-side logging runs in the browser. The client logger batches entries and sends them using navigator.sendBeacon (or fetch with keepalive) so logging does not block user interactions.
Setup
The simplest way to set up the client logger is via the Provider pattern. With the provider in place, use useSimpleLogs() in any client component to get the logger.
If you're not using the provider, initialize the SDK once near the top of your client entry point:
import { SimpleLogs } from '@simplelogs/next';
SimpleLogs.init({
clientKey: process.env.NEXT_PUBLIC_SIMPLELOGS_CLIENT_KEY!,
});
Use NEXT_PUBLIC_ prefix for env vars exposed to the browser. The client key is safe to expose to the browser because it is restricted by your origin allowlist. Never expose your server key.
:::info Automatic page/session correlation
Every call below automatically gets metadata.pageIdentifier and metadata.sessionIdentifier attached — one identifier per page load, one per browser session — and same-origin fetch calls automatically carry them as headers so server-side logs can be tied back to the page that triggered them. See Page & Session Correlation for the full picture.
:::
useSimpleLogs() hook
Returns the client logger singleton. Config is read from the nearest <SimpleLogsProvider> in the React tree.
'use client';
import { useSimpleLogs } from '@simplelogs/next';
export function CheckoutButton() {
const logger = useSimpleLogs();
return (
<button
onClick={() => logger.log({
touchpoint: 'ui/checkout/button-clicked',
message: 'Checkout button clicked',
level: 'info',
})}
>
Checkout
</button>
);
}
logger.log(options)
Logs a discrete client-side event.
logger.log({
touchpoint: 'ui/search/query-submitted',
message: 'Search performed',
level: 'info',
metadata: { query: 'running shoes', resultsCount: 42 },
});
LogOptions
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
touchpoint | string | No | — | Touchpoint name |
key | string | No | — | Correlation key |
message | string | No | — | Free-form message |
level | "info" | "warn" | "error" | "debug" | No | "info" | Severity level |
metadata | Record<string, unknown> | No | — | Arbitrary structured data |
logger.start(options) and logger.end(options)
Same semantics as the server logger — measure the duration of an operation:
logger.start({ touchpoint: 'ui/modal/open', key: 'checkout-modal' });
// ... user interacts with modal ...
logger.end({ key: 'checkout-modal', metadata: { completed: true } });
See Server Logging → start / end for the full matching rules, StartOptions, and EndOptions reference — they are identical on the client.
logger.record(options)
Records a timing entry with a pre-computed start time, end time, and duration. Use this when you already know both timestamps (e.g., from a PerformanceObserver).
const start = performance.now();
await doWork();
const end = performance.now();
logger.record({
touchpoint: 'ui/animation/frame-render',
startTime: Date.now() - (end - start),
endTime: Date.now(),
duration: end - start,
metadata: { frames: 60 },
});
RecordOptions
| Field | Type | Required | Description |
|---|---|---|---|
startTime | number | Yes | Unix timestamp (ms) |
endTime | number | Yes | Unix timestamp (ms) |
duration | number | Yes | Duration in ms |
touchpoint | string | No | Touchpoint name |
key | string | No | Correlation key |
metadata | Record<string, unknown> | No | Structured data |
flushClient()
Forces an immediate flush of the client queue. Called automatically on beforeunload if there are pending entries.
import { flushClient } from '@simplelogs/next';
// Flush before navigating away
await flushClient();
router.push('/next-page');
How client entries are delivered
The client queue flushes entries in one of two ways depending on fireAndForget:
| Mode | Transport | Retry on failure | Blocks unload |
|---|---|---|---|
fireAndForget: true (default) | navigator.sendBeacon | No | No |
fireAndForget: false | fetch with keepalive | Yes (re-queued) | No |
Beacon is appropriate for most logging scenarios — it's designed to outlive page unloads and doesn't slow down navigation. Use fetch mode when you need delivery guarantees (e.g., critical error reporting).
The module-level SimpleLogs export
If you're not using the React hook (e.g., in a utility function outside of a component), you can use the module-level SimpleLogs singleton:
import { SimpleLogs } from '@simplelogs/next';
export function trackEvent(name: string, data: Record<string, unknown>) {
SimpleLogs.log({
touchpoint: name,
metadata: data,
});
}
Reading the page/session identifiers directly
import { getPageIdentifier, getSessionIdentifier } from '@simplelogs/next';
const pageId = getPageIdentifier(); // string | undefined
const sessionId = getSessionIdentifier(); // string | undefined
Also available on the object returned by useSimpleLogs() as logger.getPageIdentifier() / logger.getSessionIdentifier(). See Page & Session Correlation for how these are generated, persisted, and propagated to the server.
This is the same object returned by useSimpleLogs() — they share the same queue.