Nuxt + Nitro + AWS Lambda + AWS X-Ray

I'm working on a new app at work for which we decided to use Nuxt 3 using universal rendering hosted on AWS Lambda.

What this means is that the app is coded in Vue.JS, which is primarily a browser-based JavaScript framework, using Nuxt as a layer around that Vue.JS app. And then when the Nuxt app is deployed, Nuxt uses another project called Nitro to build a version of the app that can run on AWS Lambda as a Node app. So, a user's browser makes an http request that hits our Lambda, which returns the Nuxt-app-rendered HTML for the app. If this sounds way more complicated than any app needs to be, I agree. But this is what we decided to do for [reasons].

One of the things this app design completely omits is the ability to trace and debug the distributed system that backs this app using AWS X-Ray. While the AWS X-Ray SDK has easy configuration instructions for Node apps, the combination of Nuxt + Nitro building the AWS Lambda bundle affords no way to configure tracing (as far as I can tell) because the AWS Lambda handler entrypoint (where you would configure X-Ray) is a generated (by Nitro) output file named .output/server/index.mjs. Here's the sum total of the documentation (which btw I believe is incorrect in addition to being... ah... shall we say, "light").

What I came up with to configure X-Ray was, rather than use .output/server/index.mjs as the Lambda entrypoint, I created a separate, plain JavaScript file to use as the entrypoint, and in that entrypoint I configured X-Ray according to the Node instructions and then loaded the Nitro-generated entrypoint and passed along the Lambda event and context to that entrypoint. Like so:

const awsXRay = require('aws-xray-sdk');
awsXRay.captureHTTPsGlobal(require('https'), false);
awsXRay.captureAWS(require('aws-sdk'));
module.exports = {
  handler: (event, context) =>
    import('./.output/server/index.mjs').then(({ handler }) => handler(event, context)),
};

I wasn't sure if the instrumentation of the https library would work. I wasn't sure if I could use dynamic import the way I did. This was a total shot in the dark, but it works!

Published
Categorized as post