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.
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,
});
}
This is the same object returned by useSimpleLogs() — they share the same queue.