Max Schmitt

March 29 2022

Error reporting with Rollbar and Next.js

When you're running code in production with actual users, error reporting is something you won't be able to live without for long.

In this blog post, I'm going to show you how to set up error monitoring with Rollbar and Next.js.

There are quite a few gotchas to watch out for, so read carefully! :)

Prerequisites

I'm going to assume that you have followed the basic Next.js setup and that you've created an account on Rollbar. Rollbar comes with a pretty generous free tier, so you don't have to spend any money until you get a serious amount of users (or errors).

Client-side and server-side errors

If you're running a universal JavaScript app, you have to be aware that you will need to setup error reporting from 2 places.

  1. Client-side errors (and promise rejections) that occur in your user's browser
  2. Server-side errors (and promise rejections) that occur on your own server (e.g. during server rendering)

Client-side error reporting with Next.js

The best way to report all client-side errors to Rollbar is by using the snippet from Rollbar's quick start guide.

The snippet is just a few kilobytes of JS and by placing it as high up as possible in your HTML, you can be sure that Rollbar will be able to report all errors that occur in your application client-side.

In Next.js we can use a custom Document to inject scripts into the <head> tag.

pages/_document.js

import Document, { Html, Head, Main, NextScript } from 'next/document'
import getConfig from 'next/config'
const { publicRuntimeConfig } = getConfig()
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<script
dangerouslySetInnerHTML={{
__html: `
var _rollbarConfig = {
accessToken: "${publicRuntimeConfig.rollbarClientToken}",
captureUncaught: true,
captureUnhandledRejections: true,
payload: {
environment: "production"
}
};
// Rollbar Snippet
!function(r){var e={};function o(n){if(e[n])return e[n].exports;var t=e[n]={i:n,l:!1,exports:{}};return r[n].call(t.exports,t,t.exports,o),t.l=!0,t.exports}o.m=r,o.c=e,o.d=function(r,e,n){o.o(r,e)||Object.defineProperty(r,e,{enumerable:!0,get:n})},o.r=function(r){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(r,"__esModule",{value:!0})},o.t=function(r,e){if(1&e&&(r=o(r)),8&e)return r;if(4&e&&"object"==typeof r&&r&&r.__esModule)return r;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:r}),2&e&&"string"!=typeof r)for(var t in r)o.d(n,t,function(e){return r[e]}.bind(null,t));return n},o.n=function(r){var e=r&&r.__esModule?function(){return r.default}:function(){return r};return o.d(e,"a",e),e},o.o=function(r,e){return Object.prototype.hasOwnProperty.call(r,e)},o.p="",o(o.s=0)}([function(r,e,o){var n=o(1),t=o(4);_rollbarConfig=_rollbarConfig||{},_rollbarConfig.rollbarJsUrl=_rollbarConfig.rollbarJsUrl||"https://cdnjs.cloudflare.com/ajax/libs/rollbar.js/2.14.4/rollbar.min.js",_rollbarConfig.async=void 0===_rollbarConfig.async||_rollbarConfig.async;var a=n.setupShim(window,_rollbarConfig),l=t(_rollbarConfig);window.rollbar=n.Rollbar,a.loadFull(window,document,!_rollbarConfig.async,_rollbarConfig,l)},function(r,e,o){var n=o(2);function t(r){return function(){try{return r.apply(this,arguments)}catch(r){try{console.error("[Rollbar]: Internal error",r)}catch(r){}}}}var a=0;function l(r,e){this.options=r,this._rollbarOldOnError=null;var o=a++;this.shimId=function(){return o},"undefined"!=typeof window&&window._rollbarShims&&(window._rollbarShims[o]={handler:e,messages:[]})}var i=o(3),s=function(r,e){return new l(r,e)},d=function(r){return new i(s,r)};function c(r){return t(function(){var e=Array.prototype.slice.call(arguments,0),o={shim:this,method:r,args:e,ts:new Date};window._rollbarShims[this.shimId()].messages.push(o)})}l.prototype.loadFull=function(r,e,o,n,a){var l=!1,i=e.createElement("script"),s=e.getElementsByTagName("script")[0],d=s.parentNode;i.crossOrigin="",i.src=n.rollbarJsUrl,o||(i.async=!0),i.onload=i.onreadystatechange=t(function(){if(!(l||this.readyState&&"loaded"!==this.readyState&&"complete"!==this.readyState)){i.onload=i.onreadystatechange=null;try{d.removeChild(i)}catch(r){}l=!0,function(){var e;if(void 0===r._rollbarDidLoad){e=new Error("rollbar.js did not load");for(var o,n,t,l,i=0;o=r._rollbarShims[i++];)for(o=o.messages||[];n=o.shift();)for(t=n.args||[],i=0;i<t.length;++i)if("function"==typeof(l=t[i])){l(e);break}}"function"==typeof a&&a(e)}()}}),d.insertBefore(i,s)},l.prototype.wrap=function(r,e,o){try{var n;if(n="function"==typeof e?e:function(){return e||{}},"function"!=typeof r)return r;if(r._isWrap)return r;if(!r._rollbar_wrapped&&(r._rollbar_wrapped=function(){o&&"function"==typeof o&&o.apply(this,arguments);try{return r.apply(this,arguments)}catch(o){var e=o;throw e&&("string"==typeof e&&(e=new String(e)),e._rollbarContext=n()||{},e._rollbarContext._wrappedSource=r.toString(),window._rollbarWrappedError=e),e}},r._rollbar_wrapped._isWrap=!0,r.hasOwnProperty))for(var t in r)r.hasOwnProperty(t)&&(r._rollbar_wrapped[t]=r[t]);return r._rollbar_wrapped}catch(e){return r}};for(var p="log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleAnonymousErrors,handleUnhandledRejection,captureEvent,captureDomContentLoaded,captureLoad".split(","),u=0;u<p.length;++u)l.prototype[p[u]]=c(p[u]);r.exports={setupShim:function(r,e){if(r){var o=e.globalAlias||"Rollbar";if("object"==typeof r[o])return r[o];r._rollbarShims={},r._rollbarWrappedError=null;var a=new d(e);return t(function(){e.captureUncaught&&(a._rollbarOldOnError=r.onerror,n.captureUncaughtExceptions(r,a,!0),e.wrapGlobalEventHandlers&&n.wrapGlobals(r,a,!0)),e.captureUnhandledRejections&&n.captureUnhandledRejections(r,a,!0);var t=e.autoInstrument;return!1!==e.enabled&&(void 0===t||!0===t||"object"==typeof t&&t.network)&&r.addEventListener&&(r.addEventListener("load",a.captureLoad.bind(a)),r.addEventListener("DOMContentLoaded",a.captureDomContentLoaded.bind(a))),r[o]=a,a})()}},Rollbar:d}},function(r,e){function o(r,e,o){if(e.hasOwnProperty&&e.hasOwnProperty("addEventListener")){for(var n=e.addEventListener;n._rollbarOldAdd&&n.belongsToShim;)n=n._rollbarOldAdd;var t=function(e,o,t){n.call(this,e,r.wrap(o),t)};t._rollbarOldAdd=n,t.belongsToShim=o,e.addEventListener=t;for(var a=e.removeEventListener;a._rollbarOldRemove&&a.belongsToShim;)a=a._rollbarOldRemove;var l=function(r,e,o){a.call(this,r,e&&e._rollbar_wrapped||e,o)};l._rollbarOldRemove=a,l.belongsToShim=o,e.removeEventListener=l}}r.exports={captureUncaughtExceptions:function(r,e,o){if(r){var n;if("function"==typeof e._rollbarOldOnError)n=e._rollbarOldOnError;else if(r.onerror){for(n=r.onerror;n._rollbarOldOnError;)n=n._rollbarOldOnError;e._rollbarOldOnError=n}e.handleAnonymousErrors();var t=function(){var o=Array.prototype.slice.call(arguments,0);!function(r,e,o,n){r._rollbarWrappedError&&(n[4]||(n[4]=r._rollbarWrappedError),n[5]||(n[5]=r._rollbarWrappedError._rollbarContext),r._rollbarWrappedError=null);var t=e.handleUncaughtException.apply(e,n);o&&o.apply(r,n),"anonymous"===t&&(e.anonymousErrorsPending+=1)}(r,e,n,o)};o&&(t._rollbarOldOnError=n),r.onerror=t}},captureUnhandledRejections:function(r,e,o){if(r){"function"==typeof r._rollbarURH&&r._rollbarURH.belongsToShim&&r.removeEventListener("unhandledrejection",r._rollbarURH);var n=function(r){var o,n,t;try{o=r.reason}catch(r){o=void 0}try{n=r.promise}catch(r){n="[unhandledrejection] error getting \`promise\` from event"}try{t=r.detail,!o&&t&&(o=t.reason,n=t.promise)}catch(r){}o||(o="[unhandledrejection] error getting \`reason\` from event"),e&&e.handleUnhandledRejection&&e.handleUnhandledRejection(o,n)};n.belongsToShim=o,r._rollbarURH=n,r.addEventListener("unhandledrejection",n)}},wrapGlobals:function(r,e,n){if(r){var t,a,l="EventTarget,Window,Node,ApplicationCache,AudioTrackList,ChannelMergerNode,CryptoOperation,EventSource,FileReader,HTMLUnknownElement,IDBDatabase,IDBRequest,IDBTransaction,KeyOperation,MediaController,MessagePort,ModalWindow,Notification,SVGElementInstance,Screen,TextTrack,TextTrackCue,TextTrackList,WebSocket,WebSocketWorker,Worker,XMLHttpRequest,XMLHttpRequestEventTarget,XMLHttpRequestUpload".split(",");for(t=0;t<l.length;++t)r[a=l[t]]&&r[a].prototype&&o(e,r[a].prototype,n)}}}},function(r,e){function o(r,e){this.impl=r(e,this),this.options=e,function(r){for(var e=function(r){return function(){var e=Array.prototype.slice.call(arguments,0);if(this.impl[r])return this.impl[r].apply(this.impl,e)}},o="log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleAnonymousErrors,handleUnhandledRejection,_createItem,wrap,loadFull,shimId,captureEvent,captureDomContentLoaded,captureLoad".split(","),n=0;n<o.length;n++)r[o[n]]=e(o[n])}(o.prototype)}o.prototype._swapAndProcessMessages=function(r,e){var o,n,t;for(this.impl=r(this.options);o=e.shift();)n=o.method,t=o.args,this[n]&&"function"==typeof this[n]&&("captureDomContentLoaded"===n||"captureLoad"===n?this[n].apply(this,[t[0],o.ts]):this[n].apply(this,t));return this},r.exports=o},function(r,e){r.exports=function(r){return function(e){if(!e&&!window._rollbarInitialized){for(var o,n,t=(r=r||{}).globalAlias||"Rollbar",a=window.rollbar,l=function(r){return new a(r)},i=0;o=window._rollbarShims[i++];)n||(n=o.handler),o.handler._swapAndProcessMessages(l,o.messages);window[t]=n,window._rollbarInitialized=!0}}}}]);
// End Rollbar Snippet
`,
}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument

Note

The Rollbar snippet contains some backticks, so make sure to escape them if you're injecting it via a template string like in the example above.

That's pretty much all it takes to report your client-side errors. If you need to report any errors manually, you can do so via window.Rollbar.error. View docs

As you can see in the script above, we're getting our Rollbar tokens from the publicRuntimeConfig.

Reporting Errors Caught by React

After including the Rollbar snippet from above, you can report errors caught by React by either adding an error boundary or, if your custom Next.js app is a class component, by catching errors in your custom App:

JS

class App extends NextApp {
// ...
componentDidCatch(error) {
window.Rollbar?.error(error)
}
// ...
}

Server-side error reporting with Next.js

To report server-side errors to Rollbar in Next.js, you need to setup a custom error page.

Note

Since this article was written, the Next.js docs no longer mention the pages/_error.js page. It still works though and is necessary if you want to report server-side errors in Next.js. The pages/500.js page is statically generated and therefore cannot access server-side errors.

If an error is thrown during the getInitialProps of a page or while React is rendering, Next.js will show the error page and pass to it the error via ErrorPage.getInitialProps.

Take note that, like the rest of Next.js, the _error page can be loaded on client and server, depending on where the error occurs. So we need to check if we're on the server before we send anything to Rollbar.

If you're only using getServerSideProps and getStaticProps in your pages, getInitialProps of your custom error page always gets called on the client side, as I have observed.

pages/_error.js

import getConfig from 'next/config'
const { serverRuntimeConfig } = getConfig()
function Error({ statusCode }) {
return <p>{statusCode ? `An error ${statusCode} occurred on server` : 'An error occurred on client'}</p>
}
Error.getInitialProps = ({ req, res, err }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404
// Only require Rollbar and report error if we're on the server
if (!process.browser) {
console.log('Reporting error to Rollbar...')
const Rollbar = require('rollbar')
const rollbar = new Rollbar(serverRuntimeConfig.rollbarServerToken)
rollbar.error(err, req, (rollbarError) => {
if (rollbarError) {
console.error('Rollbar error reporting failed:')
console.error(rollbarError)
return
}
console.log('Reported error to Rollbar')
})
}
return { statusCode }
}
export default Error

Be aware that the custom error page only gets loaded in production. You can run Next.js in production mode with yarn start.

Also make sure that you have installed the Rollbar package (yarn add rollbar or npm install rollbar).

Where to put Rollbar access tokens in Next.js

Next.js offers a really handy way to configure variables that are either exposed on only the server (serverRuntimeConfig) or both server and client (publicRuntimeConfig).

This is handy because Rollbar gives us two different access tokens – post_client_item and post_server_item.

You can set these variables in a file called next.config.js:

next.config.js

module.exports = {
serverRuntimeConfig: {
// Will only be available on the server side
rollbarServerToken: process.env.ROLLBAR_SERVER_TOKEN,
},
publicRuntimeConfig: {
// Will be available on both server and client
rollbarClientToken: process.env.ROLLBAR_CLIENT_TOKEN,
},
}

To learn more about the runtime configs, have a look at the Next.js documentation.

And that's it! By following the steps above, you now have a solid error reporting setup with Rollbar for your Next.js app.

I hope this post was helpful to you. If you have any questions, feel free to reach out to me on Twitter!