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!
Hi @smith, thanks for this very helpful gist.
I implemented this on my Remix app, but it seems that this only traces the first request/pageview.
Any subsequent client-side navigation will not be captured in the RUM Dashboard.
Is there a trick to make this work for all navigations?