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.
- Client-side errors (and promise rejections) that occur in your user's browser
- 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><scriptdangerouslySetInnerHTML={{__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 serverif (!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 siderollbarServerToken: process.env.ROLLBAR_SERVER_TOKEN,},publicRuntimeConfig: {// Will be available on both server and clientrollbarClientToken: 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!