Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Remix & Elastic APM distributed tracing correlation

We want to instrument Remix apps with the Elastic APM Node.js Agent on the server and the Elastic APM Real User Monitoring JavaScript Agent on the client.

We want the server-rendered page to have the trace id and other data available to the client, so we can correlate the traces between server and client. Elastic's distributed tracing docs explain this.

I've been able to make this work with Remix on Express, but it's super hacky, so this gist shows what I'm doing so hopefully somebody can point me to a better way to do this.

server/index.js

This is where we initialize the APM Node agent and Express. For APM, we just follow the instructions in the docs.

When we create request handler, we can use getLoadContext to put the configuration options we'll need for the RUM agent into the loader context. The loader context isn't necessarily where I want it, but this seems like the only way to pass data from the Express app into Remix.

return createRequestHandler({
          getLoadContext(req, res) {
            // this becomes the loader context
            return {
              apmRumAgentConfig: {
                serviceName: "remix-express-example-client",
                pageLoadSpanId: apm.currentTransaction.ensureParentId(),
                pageLoadTraceId: apm.currentTransaction.traceId,
                pageLoadSampled: apm.currentTransaction.sampled,
                serverUrl: apmServerUrl,
                serviceVersion: version,
              },
            };
          },
          build,
          mode: MODE,
        })(req, res, next);

app/routes/root.tsx

Since the data I need available in the loader context, I can load it via the root loader:

export const loader: LoaderFunction = ({ context }) => {
  return context;
};

This data should be available on every pageload.

app/entry.client.tsx

The best place to initialize the APM RUM agent on the client is here in entry.client.jsx.

We have access to the loader data here too, via the __remixContext global.

So, we can initialize the agent:

import { hydrate } from "react-dom";
import { RemixBrowser } from "remix";

import { init as initApm } from "@elastic/apm-rum";
initApm(__remixContext.routeData.root.apmRumAgentConfig);

hydrate(<RemixBrowser />, document);

So now, I can look at a distributed trace in APM and see the client and the server connected:

CleanShot 2021-12-13 at 11 09 35@2x

But this is bad

The seems to work, but was a very convoluted process and seems like it would not be advised. I'd like to find a more elegant way to propagate these variables from the server to the client entry. Thanks for reading. Please help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment