We understand the value of JavaScript error monitoring here at TrackJS. Consequently, we run TrackJS on all of our own web properties to log our JS errors. We’re dilligent about fixing errors as they happen, and most issues are caught in development environments, or immediately in production. However, sometimes we’ll get a sporadic error that’s hard to pin down. This is an example of how we use our own product to debug a thorny problem.

There Are Always Errors, But Are They Problematic?

The first step is determining whether you even care about an error. JavaScript errors can be noisy, and we give you great tools to ignore them if you want.

Here’s a screenshot of the rollup page in TrackJS for the error. We can see it occurs sporadically, and has only affected 6 of our users in the last 30 days. I like to start at this page when debugging to get a sense for the impact and severity of a problem.

Click to enlarge. A rollup page for the error in question: Cannot read property 'Id' of undefined.

Click to enlarge. A rollup page for the error in question: Cannot read property ‘Id’ of undefined.

While the number of users impacted is not high, the error is impacting the /account portion of the site. This is where users can change important customer information, or update their payment/billing. We really want this to work reliably!

All of the top urls are /account related, which is critical functionality for customers, and our business!

All of the top urls are /account related, which is critical functionality for customers, and our business!

This error is worth fixing (as opposed to ignoring with an Ignore Rule), the next step is diving in to the details.

The Error Details

Click on the image to enlarge. A screenshot of the error details.

Click on the image to enlarge. A screenshot of the error details.

The first thing I notice in this case is the stack trace. It’s from an older part of our JavaScript code. I can tell because there’s no sourcemap generated (we use backbone.js with no transpilation step - gasp - for some parts of the site still). Fortunately, we don’t need fancy tricks. The stack frames, coupled with our penchant for long descriptive function names, means I know exactly which file this code is in.

Descriptive stack frames mean I know which file this error originates in.

Descriptive stack frames mean I know which file this error originates in.

The error is coming from our checkout/billing/subscription JavaScript. This code is responsible for interfacing with Stripe, handling payments and billing updates, and making sure customers can subscribe. This is mission critical code!

The Plot Thickens

But something doesn’t make sense. We only load the JavaScript in question on the /account/subscription page. From the Telemetry Timeline I can see the error occurred on the /account/team page, only about half a second after we navigated.

The code where the error is coming from should never run on this url.

The code where the error is coming from should never run on this url.

And anyways, we were coming from the /shared/errors page, not anything subscription related. This code should never be running! But sure enough I can see from the timeline that Stripe is getting loaded, right after the user navigated to /shared/errors. You can see they clicked the link, we made a PJAX request, the URL changed via pushState and then the Stripe code loads.

We can see the network request for the Stripe assets, even though it's not a billing page.

We can see the network request for the Stripe assets, even though it’s not a billing page.

I double checked the server template for /shared/errors to make sure there’s no Stripe or billing related code. There isn’t. But… when a potential customer’s trial ends, we redirect (almost) every path to the subscription page. The user can still navigate to the /account section and make changes, but instead of seeing their data they see a subscribe page for other routes. This could explain why the code is running on weird URLs. But why is it breaking?

The Network Ain’t Reliable

We don’t want to load all the Stripe JavaScript unless we have to. So we lazy load their checkout.js file only if the user is on the subscription page. However, networks can be slow. In the event that the user clicks fast enough, and the checkout.js file is slow to load, we’ll attempt to initialize the rest of the JavaScript on the wrong page!

The code we use to boostrap Stripe, and then the rest of our page.

The code we use to boostrap Stripe, and then the rest of our page.

Reproducing the Problem Is Easy Now

Once I realized that all kinds of URL paths could be serving the subscription JavaScript, but only to trial users, it made sense why this error was so sporadic. Further, the only way make it happen is to navigate quickly from one page to the next (on a somewhat congested network). Since we use PJAX, all JavaScript stays resident in memory even across page transitions, and our lazy loading of the Stripe checkout JavaScript created a perfect storm.

I was quickly able to reproduce the problem by creating an expiring trial account. The big concern I had was whether this error was impacting billing actions. I was fortunately able to confirm that it was not. Without being able to reproduce the problem, there’s no way to know for sure though! Since the severity of this error is low, we’ll triage it to figure out whether it’s worth adding a defensive check, or better just to ignore it as there is no user impact.

Summary

Cannot read property 'Id' of undefined errors happen because the code is executing in an unexpected context. In our case, it’s because the user switched pages quickly, which is pretty likely to happen for highly-interactive applications like TrackJS. Unexpected things happen in production!

TrackJS gives you a lot more tools to figure out what’s happening than I covered today. Please sign up for a free trial and give it a try!