# Frontend observability

Nais provides frontend observability through the [Grafana Faro Web SDK](https://www.npmjs.com/package/@grafana/faro-web-sdk). Faro runs in the browser and instruments your app, shipping telemetry through the platform collector (**Alloy**) into the shared observability stores: errors and events land in **Loki**, browser spans in **Tempo**, and Core Web Vitals in **Mimir**. You then view and triage it all in [Nais APM](https://grafana.ssb.cloud.nais.io/a/nais-apm-app).

With 95+ applications already using Faro, it's the standard way to monitor frontend applications on Nais.

## What you get

- **[Core Web Vitals](https://web.dev/vitals/)** — TTFB, FCP, CLS, LCP, INP (collected automatically)
- **Logging** — `console.info`, `console.warn`, and `console.error` messages
- **Exceptions** — stack traces with [automatic sourcemap deobfuscation](/observability/frontend/how-to/sourcemaps.md)
- **Custom events and metrics** — anything you want to track
- **[Tracing](/observability/frontend/how-to/trace-propagation.md)** — OpenTelemetry-based browser traces, optionally connected to backend spans

## Get started

1. **Any frontend app:** [🎯 Set up Faro](/observability/frontend/how-to/setup-faro.md)
2. **Next.js App Router:** [:simple-nextdotjs: Set up Faro with Next.js](/observability/frontend/how-to/setup-nextjs.md)
3. **Want end-to-end traces?** [Connect frontend to backend](/observability/frontend/how-to/trace-propagation.md)
4. **Stack traces look minified?** [Sourcemaps](/observability/frontend/how-to/sourcemaps.md) · [Troubleshooting](/observability/frontend/reference/troubleshooting.md)

## Recommended configuration

After the basic setup, check that you have these configured:

| Setting | Why it matters | Guide |
| --- | --- | --- |
| `beforeSend` PII filtering | Faro captures URLs and console output that may contain fødselsnummer or tokens | [Privacy and sensitive data](/observability/frontend/how-to/setup-faro.md#privacy-and-sensitive-data) |
| `propagateTraceHeaderCorsUrls` | Without this, browser traces never connect to your backend spans | [Trace propagation](/observability/frontend/how-to/trace-propagation.md) |
| `paused` on localhost | Prevents local development noise from polluting production dashboards | [Local development](/observability/frontend/how-to/setup-faro.md#local-development) |
| `app.version` | Lets you filter errors and metrics by deploy | [Setting app.version](/observability/frontend/how-to/setup-faro.md#setting-appversion) |
| Error boundary | Catches React rendering errors that are otherwise silently lost | [React error boundaries](/observability/frontend/how-to/setup-faro.md#react-error-boundaries) |

## Collector endpoints

The collector URL tells Faro where to send telemetry data. If you use the [auto-configuration](/observability/frontend/how-to/setup-faro.md#auto-configuration) in `nais.yaml`, this is set for you.

| Collector endpoint | Environment |
| --- | --- |
| `https://telemetry.external.prod.ssb.cloud.nais.io/collect` | `prod` |
| `https://telemetry.external.dev.ssb.cloud.nais.io/collect` | `dev` |

On-premises clusters are not supported.

## Inspecting frontend data in Grafana

Find your app in the [Nais APM](https://grafana.ssb.cloud.nais.io/a/nais-apm-app/services) service inventory and open it. Browser telemetry surfaces on two tabs:

- **Frontend** — Core Web Vitals, pageloads, sessions, per-page performance, browser breakdown, and web-vitals attribution (what's driving LCP, INP, and CLS).
- **Issues** — browser exceptions grouped into issues, side by side with backend errors. Filter the source to **Browser** to isolate frontend errors, then open an issue for its stack trace and impact.

![Nais APM Issues tab filtered to browser-sourced exceptions](/assets/nais-apm-service-navno-issues-frontend.png)

For deeper analysis, use the [Explore view](https://grafana.ssb.cloud.nais.io/explore) with one of the Loki data sources. Here are some useful queries:

```logql
# All frontend logs for your app
{app_name="my-app"} | logfmt

# Exceptions only
{app_name="my-app", kind="exception"} | logfmt

# Slow Largest Contentful Paint (> 2.5s)
{app_name="my-app", kind="measurement"} | logfmt | web_vitals_lcp > 2500
```

Traces are available from data sources ending with `-tempo`. Use [TraceQL](/observability/tracing/reference/traceql.md) to query them.

## Local development

For local development, check out the [tracing demo repository](https://github.com/nais/tracing-demo) and run `docker-compose up`. This gives you a local Grafana, Loki, and Tempo stack to test against. See the README in that repository for details.

## Guides

| Guide | Description |
| --- | --- |
| [Set up Faro](/observability/frontend/how-to/setup-faro.md) | Install, configure, and deploy Faro in any frontend app |
| [Next.js App Router](/observability/frontend/how-to/setup-nextjs.md) | Faro integration for Next.js with App Router |
| [Custom metrics](/observability/frontend/how-to/custom-metrics.md) | Send custom measurements and query them in Grafana |
| [Trace propagation](/observability/frontend/how-to/trace-propagation.md) | Connect frontend traces to backend spans |
| [Sourcemaps](/observability/frontend/how-to/sourcemaps.md) | How stack trace deobfuscation works |
| [Auto-configuration](/observability/frontend/reference/auto-configuration.md) | Generated config values and environment variables |
| [Troubleshooting](/observability/frontend/reference/troubleshooting.md) | CSP, CORS, and common issues |

## Further reading

- [Grafana Faro Web SDK docs](https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/)
- [Grafana Faro Web SDK on npm](https://www.npmjs.com/package/@grafana/faro-web-sdk)
- [Grafana Faro Web SDK changelog](https://github.com/grafana/faro-web-sdk/blob/main/CHANGELOG.md)
- [Grafana Faro Next.js example](https://github.com/grafana/faro-nextjs-example)
