destroytoday.com

Sideways progress

Last night, in a moment fueled with creative energy, I sat down to making forward progress with the site. I didn’t know exactly what I wanted to work on, so I started looking for the next most valuable thing to work on. In doing so, my eye caught a seemingly small issue with the RSS feed—it was rendering the articles without any of my custom renderers, so any images or hyperlinks appeared as Contentful IDs. This definitely felt worth prioritizing first, and didn’t seem like a time sink. I’d simply extract my renderers out of the Article component, so they could be used by both the Article and the RSS feed, which was on my to-do list anyway.

I approached the refactor by keeping each custom renderer in a /lib/contentful/renderers folder, in their own JavaScript file, with each exporting a render function. Then, I mapped these methods to the Contentful documentToHtmlString options for a much cleaner, predictable design—rather than keeping all the render functions inline. This made the renderers easier to find, straightforward to build, and well-isolated to test if I wanted to.

The refactor felt great and didn’t take too long, so I pushed the change, and checked the deploy—not expecting anything out of the ordinary. Instead, I found that none of the articles in the production RSS feed rendered. The RSS feed looked fine locally, but on production, it only displayed the feed metadata—no error or anything in the logs. Something told me this was going to consume my night. It did.

Since the bug itself was in production, I could only test it by deploying, which took nearly a minute each time. I started by double-checking that there were articles in the feed—there were. Next, I checked the output of my extracted render method with all the custom renderers—empty. I then tried wrapping the render in a try { ... } catch to see if any errors were coming through, but being silenced for some reason—yes, but the returned “error” was an empty object.

I knew the issue was with my custom renderers, but couldn’t put my finger on it, so I took the most technically advanced approach I could think of—commenting out code and uncommenting it. I started with all the renderers, then re-added them one-by-one until I discovered the culprit—my <code> renderer. This was a surprise because there’s not much to it. The renderer simply checks for the syntax language using the regular expression I wrote about in a previous article, then uses Prism.js to highlight the code.

I continued to comment out code throughout the render function until I could easily toggle the bug, which existed in the following lines of code, which were responsible for extracting the specified language from a code block:

const match = code.match(/^(?<language>\w+):`(?<code>[\s\S]+)`$/)

if (match) {
  language = match.groups.language
  code = match.groups.code
}

I spent a few minutes scratching my head until it dawned on me—is there a chance my production environment differs from my local dev environment? *gasp* No... It couldn’t be... We live in an age where everything just works... I nervously added console.log(process.version) to check the Node version. Locally, it was latest recommended version, 12.13.1, but in production, my Now environment used Node 8, which doesn’t support named groups in regular expressions. I was relieved to find the issue, but any sort of clue would’ve saved me a couple hours.

To fix the issue, and maintain consistency between my local and production dev environments, I specified the target Node version in my package.json:

{
  "engines": {
    "node": "12.13.1"
  }
}

I also added a .nvmrc file to my codebase to specify the version, so I could run nvm use to use the right Node version locally. I’d love to have it specified in one place, but Now only seems to use the version from my package.json. I didn’t spend long looking for a way, but I’m sure there must be one.

I typically get down on myself for experiences like this because I often see it as lost time that could’ve been spent towards forward progress instead of troubleshooting an issue. At the same time, I accomplished a few things. I mirrored my RSS feed to the actual site; I refactored my renderers to be easier to navigate and test; and I synchronized my Node version between local dev and production—avoiding an inevitable head-scratcher down the road. I’m still a bit bummed that I spent so long troubleshooting, but that’s the life of a dev.