RUM can leave questions unanswered.
Honeycomb for Frontend Observability doesn’t.
Are you trying to wire your React application to Honeycomb, but running into some challenges understanding how our instrumentation works with React?
In this article, I’ll lay out approaches for wiring Honeycomb to client-side only React so you can ingest your telemetry into Honeycomb and take advantage of the Web Launchpad. This telemetry sends semantically-named attributes, and can be used with any OTLP destination.
These examples use a React application created with Vite. The advice here applies to React apps that are not using server-side rendering. Watch this space for more information about using Next.js.
Easiest approach: Configure HoneycombWebSDK
before booting React
Since a React application usually boots from something like src/main.ts|.js
, configure your OpenTelemetry browser instrumentation here before booting React. This has the benefit of only running once for your browser session and is fully configured before any services start up.
Let’s initialize your React application:
main.ts|.js
: Step 1 – wire up telemetry before starting React
import { createRoot } from 'react-dom/client' import './index.css' import App from './App' import {StrictMode} from "react"; import installOpenTelemetry from './otel-config'; // avoid double-render problem by wiring up the // Honeycomb OpenTelemetry Web SDK wrapper // outside of a render process installOpenTelemetry(); // now, boot React! createRoot(document.getElementById('root')!).render( <StrictMode> <App /> </StrictMode> )
Now, we’ll create a file with a function that does the wiring (see our docs and additional samples for more details):
src/otel-config.ts|.js
: Step 2 – create a set of defaults
import { HoneycombWebSDK } from '@honeycombio/opentelemetry-web'; import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web'; // some telemetry instrumentation requires default settings // so we create a set of sensible defaults. const defaults = { // don't create spans for all of the network traffic, otherwise // we'll get 10x the spans we normally care about ignoreNetworkEvents: true, // Which outgoing service calls to servers will contain the // traceparent header to pass our trace information inward, // so our fetch requests will be part of an end-to-end trace. // otherwise you'll get disconnected front-end and back-end traces!! // for this case, we're allowing zero or more characters in the // server name so we are propagating traces to all outbound fetch // calls. propagateTraceHeaderCorsUrls: [ /.*/g ) ] }
Note: If you don’t set up your propagateTraceHeaderCorsUrls
entries here and point them at your application backend endpoints, you won’t send the proper W3C traceparent
header to any backend services you call from React via instrumented network calls from either the fetch
or XMLHttpRequest
APIs. This means you won’t end up seeing frontend-to-backend spans in your traces.
src/otel-config.ts|.js
: Step 3 – initialize the HoneycombWebSDK
... export default function installOpenTelemetry() { try { // this SDK installs OpenTelemetry-JS for a web browser, and // adds automatic instrumentation for Core Web Vitals and other // features. const sdk = new HoneycombWebSDK({ contextManager: new StackContextManager(), serviceName: 'react-frontend', instrumentations: [ getWebAutoInstrumentations({ '@opentelemetry/instrumentation-xml-http-request': defaults, '@opentelemetry/instrumentation-fetch': defaults, '@opentelemetry/instrumentation-document-load': defaults '@opentelemetry/instrumentation-user-interaction': defaults }) ] }); // start up the SDK, wiring up OpenTelemetry for JS sdk.start(); } catch (e) { console.log(`An error occurred wiring up Honeycomb...`); console.error(e); } }
Viewing telemetry for a React application in Honeycomb
If you have a standard Honeycomb account, you can use web telemetry along with your other telemetry data.
Run a Honeycomb query
For this example, I’ll use the Query Builder against our datasource react-frontend
(which we defined in our instrumentation as our service-name
), using:
- Visualization:
COUNT
(how many in each time period) - Where:
library.name = @opentelemetry/instrumentation-fetch
(only showsfetch
calls) - Group By:
http.status_text
(to view good and bad calls)
This query looks like this:

If I scroll down a bit, I see options to view traces. Here they are:

View an OpenTelemetry trace in Honeycomb
I clicked on one of the trace IDs that had a root name of Submit
, which was triggered by clicking a form submit button in a frontend form. Since I’ve configured Honeycomb to include user event tracing, a button click is traced by default.
This trace shows that we encountered a database error that we didn’t plan for, and that error failed our POST
. Clicking on the span near the bottom shows the error message as one of the attributes (status.message
on the pg.query: INSERT library
span):

Viewing the application from the Honeycomb for Frontend Observability Web Launchpad
In an account configured with Honeycomb for Frontend Observability, the environment landing page becomes the Web Launchpad. Here, you can see helpful charts and statistics based on the telemetry emitted from the HoneycombWebSDK
via opentelemetry-js
:

But I want to create a component that does the initialization
Alternatively, you might approach instrumentation by mounting a component. Maybe you want to defer telemetry until a particular parent route opens up (do you?).
While instrumenting after the React application has begun is not necessarily a problem, it is less direct. However, samples exist that show this approach, so let’s review it.
In the component’s render method, you could create an effect to boot React on the mounting of a component. Simply call the Collector setup method in a useEffect
hook on the way up. Note that you don’t provide any values in the hook’s dependency array, so the effect never re-runs.
ObservabilityConfigurer.ts
: Call the instrumentation script on an initial loading effect
import { installOpenTelemetry } from './otel-config'; export default function ObservabilityConfigurer() { useEffect(() => { installOpenTelemetry(); }, []); return null; // render nothing, this is just a component // to facilitate wiring up Honeycomb }
You can then mount the component within your top-level component. The useEffect
hook above ensures that this only runs on the initial render of your component.
src/Application.tsx
: Now, call your top-level component
import ObservabilityConfigurer from './ObservabilityConfigurer'; export default function App() { return ( <> <ObservabilityConfigurer /> {/* Your top-level components here */} </> ); }
Go with the simplest approach that makes sense for you, and do it as early as you can to avoid missing any key telemetry. Executing the script before loading React is the easiest way to isolate it from the rest of your components.
For more help
The open source Honeycomb OpenTelemetry Web project provides the HoneycombWebSDK
wrapper used in this blog post and sends telemetry compatible with the Web Launchpad.
Have questions about instrumenting React applications with OpenTelemetry or troubleshooting your configuration using Honeycomb? You can request office hours with me, check our detailed documentation, or join the Honeycomb Pollinators Slack. I’ll be happy to help you get going.