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.
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);
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.
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:
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!
@depsimon I haven't revisited this since originally making it. I don't remember if this was a problem then or if it's something new.
The RUM agent exposes React Router components: https://www.elastic.co/guide/en/apm/agent/rum-js/4.x/react-integration.html#_instrumenting_application_routes
I'm not sure if it's required to use these components or if they work with how Remix uses React Router.
So, using the component from the RUM Agent might work, and if it doesn't I think you might need to instrument the route change events by customizing/configuring React Router.