Next.js
Error reporting with Rollbar and Next.js | Support Level: Supported
Overview
Rollbar supports both the pages and app routing versions of Next.js. While there are some difference in the usage between the two, the setup remains the same.
Pre-requisites
- Next.js (both pages and app routing is supported)
- Rollbar account
- Client-side access token
- Server-side access token
- Rollbar snippet
Installation
Configuration
Add your access tokens
You can add your Rollbar access tokens as environment variables. To get started in local development, add the following to your .env.local
:
NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN=x
ROLLBAR_SERVER_TOKEN=y
To get the tokens, create a new project in Rollbar, skip through onboarding, and then navigate to the Project Access Tokens page (Projects -> name of project -> Project Access Tokens). Replace x
with the post_client_item
token and y
with the post_server_item
token.
The client token will be exposed in the browser and the server token is a secret.
Usage
In NextJS there are two parts to the rendering of a page: the server side and the client side. As a result, we need to ensure that the app is configured both for the server and the client side rendering. It is a simple process that this page will guide you through.
Rollbar will be able to capture these types of errors:
- 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 Browser JS 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 tag. Place the following code in your pages/_document.js
. If you don't have that file yet, create it.
// 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: "${process.env.NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN}",
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){"use strict";var n=o(1),t=o(5);_rollbarConfig=_rollbarConfig||{},_rollbarConfig.rollbarJsUrl=_rollbarConfig.rollbarJsUrl||"https://cdn.rollbar.com/rollbarjs/refs/tags/v2.26.1/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){"use strict";var n=o(2),t=o(3);function a(r){return function(){try{return r.apply(this,arguments)}catch(r){try{console.error("[Rollbar]: Internal error",r)}catch(r){}}}}var l=0;function i(r,e){this.options=r,this._rollbarOldOnError=null;var o=l++;this.shimId=function(){return o},"undefined"!=typeof window&&window._rollbarShims&&(window._rollbarShims[o]={handler:e,messages:[]})}var s=o(4),d=function(r,e){return new i(r,e)},c=function(r){return new s(d,r)};function u(r){return a((function(){var e=this,o=Array.prototype.slice.call(arguments,0),n={shim:e,method:r,args:o,ts:new Date};window._rollbarShims[this.shimId()].messages.push(n)}))}i.prototype.loadFull=function(r,e,o,n,t){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. e;if(void 0===r._rollbarDidLoad){e=new Error("rollbar.js did not load");for(var o,n,a,l,i=0;o=r._rollbarShims[i++];)for(o=o.messages||[];n=o.shift();)for(a=n.args||[],i=0;i<a.length;++i)if("function"==typeof(l=a[i])){l(e);break}}"function"==typeof t&&t(e)}()}})),d.insertBefore(i,s)},i.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(","),f=0;f<p.length;++f)i.prototype[p[f]]=u(p[f]);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 l=new c(e);return a((function(){e.captureUncaught&&(l._rollbarOldOnError=r.onerror,n.captureUncaughtExceptions(r,l,!0),e.wrapGlobalEventHandlers&&t(r,l,!0)),e.captureUnhandledRejections&&n.captureUnhandledRejections(r,l,!0);var a=e.autoInstrument;return!1!==e.enabled&&(void 0===a||!0===a||function(r){return!("object"!=typeof r||void 0!==r.page&&!r.page)}(a))&&r.addEventListener&&(r.addEventListener("load",l.captureLoad.bind(l)),r.addEventListener("DOMContentLoaded",l.captureDomContentLoaded.bind(l))),r[o]=l,l}))()}},Rollbar:c}},function(r,e,o){"use strict";function n(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.exports={captureUncaughtExceptions:function(r,e,o){if(r){var t;if("function"==typeof e._rollbarOldOnError)t=e._rollbarOldOnError;else if(r.onerror){for(t=r.onerror;t._rollbarOldOnError;)t=t._rollbarOldOnError;e._rollbarOldOnError=t}e.handleAnonymousErrors();var a=function(){var o=Array.prototype.slice.call(arguments,0);n(r,e,t,o)};o&&(a._rollbarOldOnError=t),r. 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)}}}},function(r,e,o){"use strict";function n(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=function(r,e,o){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&&n(e,r[a].prototype,o)}}},function(r,e,o){"use strict";function n(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])}(n.prototype)}n.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=n},function(r,e,o){"use strict";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. These are escaped in the code above. If you update the snippet in the future, be sure the backticks are escaped with \
.
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.
As you can see in the script above, we're getting our Rollbar tokens from process.env.
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:
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.
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.
First, install the Rollbar package (yarn add rollbar or npm install rollbar).
Second, add the following as the file pages/_error.js
:
// 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(process.env.ROLLBAR_SERVER_TOKEN)
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
or npm run build && npm run start
.
Thanks
Many thanks to Max Schmitt, for the blog post and content for using Rollbar with Next.js from which this post is adapted.
Original Blog Post: https://maxschmitt.me/posts/error-reporting-rollbar-nextjs/
Updated about 2 months ago