Litestore · Developer docs
Platform services

Observability

Set up error monitoring and analytics, and track down why an event didn't arrive.

Litestore reports errors and performance data to Sentry, and it sends product analytics to the destinations you configure. These two systems are unrelated, and the sections below cover both.

Revenue$48.2k+12%
Orders1,284+8%
Avg. order$37.50+4%
Conversion2.9%+0.3
JanMarMayJulSepNov
Page views and purchases reach the analytics tools only after you configure each destination and confirm it collects data.

Optional services

The app runs without either system. If you leave a provider unconfigured, you lose that data, but the store stays up.

That choice has a side effect to keep in mind from the start. Nothing described here fails loudly. A misconfigured destination looks exactly like a destination that has nothing to report.

Sentry

Next.js has three runtimes, and Litestore sets up Sentry in four files to cover them.

instrumentation.ts
instrumentation-server.ts
instrumentation-edge.ts
instrumentation-client.ts

instrumentation.ts is the register hook. It loads the server or edge file depending on the runtime, and it exports onRequestError. instrumentation-server.ts covers the Node server, instrumentation-edge.ts covers the Edge runtime, and instrumentation-client.ts covers the browser.

Each of the three runtime files starts Sentry only when NEXT_PUBLIC_SENTRY_DSN is set. Without a DSN, no runtime creates a Sentry client.

Sentry activation

The server and edge files start Sentry whenever NODE_ENV isn't development, and the browser file only in production. Errors from bun run dev never show up in the issue list.

The build wrapper follows the same rule. Litestore applies withSentryConfig only on a production Vercel build, and source maps upload only in CI.

The browser file has one exception. If you set NEXT_PUBLIC_SENTRY_DEV to true, the browser SDK starts outside production too. Use it when you need to prove that browser reporting works.

Sampling

All three runtimes send 10% of traces.

The browser also records session replays. It masks all text and blocks all media. It records 10% of sessions, and every session that has an error.

Each runtime drops errors that count as noise before sending anything. All runtimes drop chunk-loading failures and the Failed to fetch and NetworkError type errors. The browser also drops ResizeObserver loops, errors from browser extensions and ZodError form validation errors, since those come from normal user input and not from a bug.

In production, Sentry drops console breadcrumbs.

Because of these filters, a quiet Sentry doesn't prove the app is error-free. To test the integration, throw an error that isn't on these lists.

Server-side forwarding

modules/analytics/server-forward.ts sends events to three destinations. Each destination checks only its own keys, so one works without the others.

DestinationHow Litestore sends to itKeys it needs
PostHogIts capture APIPOSTHOG_API_KEY
GA4The Measurement ProtocolGA4_MEASUREMENT_ID and GA4_API_SECRET
MetaThe Conversions APIMETA_PIXEL_ID and META_ACCESS_TOKEN

Litestore sends to all three with Promise.allSettled. If one destination is slow or fails, the request doesn't wait for it.

Two places call this code. The /api/track route handles beacons from the browser and adds details from the request. The payment webhook sends purchase events and adds details from the order metadata.

The webhook used to POST to /api/track over HTTP. That approach had three problems. It started a second serverless function for every purchase. It ran the shopper rate limiter against the function's own IP. And it couldn't add any details, because an internal request has no shopper cookies.

Meta events include an eventId. When the browser Pixel and the Conversions API send the same id, Meta counts them as one event instead of two.

Plausible only runs in the browser

Plausible is not one of the server destinations. It runs as a browser tracker, so it never receives an event that doesn't happen in a browser, such as an order created by a webhook.

Browser trackers

modules/analytics/consent-gated-trackers.tsx loads nothing until the shopper gives consent.

The component waits until it has read the shopper's consent and found it granted. During server rendering and the first paint it renders nothing, because consent can only be read in the browser.

Consent groupTrackers it loads
AnalyticsPlausible and GA4
MarketingThe Meta Pixel and the TikTok Pixel

Each tracker also needs its own public environment variable. If you never configured a tracker, granting its group doesn't load it.

The component checks again when the consent-changed event fires and when a storage event arrives from another tab.

There is one gap, and it comes from how scripts work, not from a bug. Removing a script from the page can't undo a pixel that has already loaded. Since nothing loads before consent, this only matters when a shopper withdraws consent they already gave in the same session. The next page load respects the change.

Production check

Deploying the code doesn't turn data collection on. You still need to enable each destination and prove it collects data in the environment you intend.

Test log line

Then find it in the deployment logs.

Test error

Confirm that its stack trace resolves to your source.

Page views

Confirm the page views arrive.

Use separate Sentry environments or projects for preview and production. That way a test error never pages someone about production.

Missing events

Start with the narrowest check and work outward.

Most of these cases come down to one question:

Which runtime should this event have started in, and does that runtime report anywhere?

On this page