Next.js: How to catch and handle errors to report logs on server side

Created on 2 May 2017  ·  74Comments  ·  Source: vercel/next.js

Hi,
I'm in the situation where we want to sent errors, both on server and client side, to Sentry tool.

Our app uses Express as a custom server. Basically we create an express app, apply some middlewares but delegate all the real job to the next.js handle:

  const app = nextJs({ dev: process.env.NODE_ENV !== 'production' });
  const handler = routes.getRequestHandler(app);
  const expressApp = express();

  ...
  ...

  expressApp.use(morgan('combined', { stream: logger.stream }));
  expressApp.use(statsdMiddleware);

  // Add security
  expressApp.use(helmet());

  // Sentry handler
  expressApp.use(sentry.requestHandler());

  // Load locale and translation messages
  expressApp.use(i18n);

  // Next.js handler
  expressApp.use(handler);

  // Sentry error handler.
  // MUST be placed before any other express error handler !!!
  expressApp.use(sentry.errorHandler());

With this approach next.js takes control over the rendering process and any error is catch by next.js and the only way I have to process it is overriding the _error.js page file.

Within that _error.js file I need a universal way to report errors to Sentry. Currently there are two libraries (raven for node and raven-js por javascript). The proble is I can't import both of them because raven works for SSR but fails when webpack builds the bundle, and also raven-js fails due XMLHTTPRequest dependency too.

Is there a way I can be notified for next.js error on server side?

story feature request

Most helpful comment

Gotta love _unortodox_ solutions

function installErrorHandler(app) {
  const _renderErrorToHTML = app.renderErrorToHTML.bind(app)
  const errorHandler = rollbar.errorHandler()

  app.renderErrorToHTML = (err, req, res, pathname, query) => {
    if (err) {
      errorHandler(err, req, res, () => {})
    }

    return _renderErrorToHTML(err, req, res, pathname, query)
  }

  return app
}
// ¯\_(ツ)_/¯

All 74 comments

For logging errors in the client side we have been doing the following:
https://gist.github.com/jgautheron/044b88307d934d486f59ae87c5a5a5a0

It basically sends errors to the server, that are in the end printed to stdout and caught by our Docker logging driver.
We have successfully caught SSR errors with react-guard with no need to override core Next files.

I'm also having this problem. I wonder: would simply returning a rejected promise from next's handleRequest be sufficient? That is, changing the code here to be:

handleRequest (req, res, parsedUrl) {
  // .....snip....

   return this.run(req, res, parsedUrl)
     .catch((err) => {
       if (!this.quiet) console.error(err)
       res.statusCode = 500
       res.end(STATUS_CODES[500])

       // rethrow error to create new, rejected promise 
       throw err;
     })
}

Then, in user code:

const app = nextJs({ dev: process.env.NODE_ENV !== 'production' });
const nextJsHandler = app.getRequestHandler();
const expressApp = express();

app.prepare().then(() => {
   // invoke express middlewares
   // ...

   // time to run next
   expressApp.use(function(req, res, next) {
     nextJsHandler(req, res).catch(e => {
       // use rejected promise to forward error to next express middleware
       next(e)
     })
   });

   // Use standard express error middlewares to handle the error from next
   // this makes it easy to log to sentry etc.
})

@arunoda @rauchg Do you think the change I proposed immediately above would work? If so, happy to submit a PR

Agree to re-throw an error so we can play around with it.
Also need to re-throw at renderToHTML too...

I am also in the situation where we want to sent errors, both on server and client side, to a service similar to Sentry.

I believe that the most valuable feature of such services/tools is to report issues that are most unexpected, which in my experience are uncaught errors in the wild (ie. client-side). Unfortunately, as previously stressed out in related issue #2334, Next.js’ client-side handlers keep these errors to themselves, with no possible way to pass them to Sentry of such other tool.

What has bitten us particularly hard is this: a properly server-side rendered page is re-rendered as an error page if an uncaught exception occurs before React rendering on the client-side. This can be seen as either a great feature, but also a frustrating developer experience, as it essentially ruins the benefits of serving an already rendered document, on a surprisingly portion of clients.

Here is a sample page that illustrates the problem:

import React from 'react';

// Works well in Node 8, but crashes in Chrome<56, Firefox<48, Edge<15, Safari<10, any IE…
const whoops = 'Phew, I made it to the client-side…'.padEnd(80);

export default () => <pre>{whoops}</pre>;

The above code can be perfectly server-side-rendered and delivered to the client, only to become a Flash Of Unwanted Content before the whole page is replaced on the client-side by the dreaded ”An unexpected error has occurred” message on most browsers, without any possible way to report the error to Sentry (or any other service/tool).

This ”error swallowing” also prevents any leverage of the standard onerror handler, which most client-side error reporting tools hook onto (or the more modern, but not widespread, onunhandledrejection, which may be more suitable given the async nature of client-side code).

Possible client-side solution

As far as I can tell, this type of pre-React client-side error is swallowed in the try/catch block in Next.js’ client/index.js where the Component about to be rendered is re-assigned to the value of ErrorComponent (currently lines 67-72).

Dear Next.js authors and maintainers, for the sake of control of what is rendered, what would you think would be acceptable/possible among the following ideas:

  • introduce a hook in that catch block in client/index.js for handling this kind of error?
  • transmit the error to an onerror/onunhandledrejection handler, if any is detected?
  • provide an option to rethrow the error?
  • provide an option to not display the error component?
  • provide an option to display a custom error component?

introduce a hook in that catch block in client/index.js for handling this kind of error?
transmit the error to an onerror/onunhandledrejection handler, if any is detected?

This is something we've talked about internally. And we'll be addressing soon.

I'm using Sentry.io to report errors and the solution we apply is:

1- Configure raven-node on server side
2- Configure ravenjs on client side (we made that on _document.

With this two steps we catch any unhandled exceptions both on client and server.

3- Create an _error page. Any error produced once nextjs handles the request (no matter if client or server side) that page is rendered. In the getInitialProps method of the _error page we report the error to sentry.

The way to decide how to load if raven-node or ravenjs is solved importing dynamically depending if we are in client const Raven = require('raven-js'); or server side const Raven = require('raven');.
Note we have configured webpack to not bundle the raven module (the server side one) updating next.config.js with:

const webpack = require('webpack');

module.exports = {
  // Do not show the X-Powered-By header in the responses
  poweredByHeader: false,
  webpack: (config) => {
    config.plugins.push(new webpack.IgnorePlugin(/^raven$/));
    return config;
  },
};

@acanimal

2- Configure ravenjs on client side (we made that on _document.

Can you show me how did you configured raven-js on your _document.js? It's not working for me, when any error occur nothing happens on sentry.

Do I need to send the all the errors manually at _error.js page to sentry?

// _document constructor
constructor(props) {
    super(props);
    Raven
      .config('...')
      .install();
}

Next.js still outputs errors to console.error on the server as long as you don't set it to quiet.

With Sentry, you can enable autoBreadcrumbs to capture this output, and then capture your own message manually. The title will be less descriptive, but it will still contain the full stack trace.

Example implementation:

const express = require('express');
const nextjs = require('next');
const Raven = require('raven');

const dev = process.env.NODE_ENV !== 'production';

// Must configure Raven before doing anything else with it
if (!dev) {
  Raven.config('__DSN__', {
    autoBreadcrumbs: true,
    captureUnhandledRejections: true,
  }).install();
}

const app = nextjs({ dev });
const handle = app.getRequestHandler();

const captureMessage = (req, res) => () => {
  if (res.statusCode > 200) {
    Raven.captureMessage(`Next.js Server Side Error: ${res.statusCode}`, {
      req,
      res,
    });
  }
};

app
  .prepare()
  .then(() => {
    const server = express();

    if (!dev) {
      server.use((req, res, next) => {
        res.on('close', captureMessage(req, res));
        res.on('finish', captureMessage(req, res));
        next();
      });
    }

    [...]

    server.get('/', (req, res) => {
      return app.render(req, res, '/home', req.query)
    })

    server.get('*', (req, res) => {
      return handle(req, res)
    })

    server.listen('3000', (err) => {
      if (err) throw err
      console.log(`> Ready on http://localhost:${port}`)
    })
  })
  .catch(ex => {
    console.error(ex.stack);
    process.exit(1);
  });

This is a very contrived example adapted from our actual code. I haven't tested it in this form. Let me know if it breaks.

Of course, it would still be best if Next.js could pass those errors around to Express, so that we can use the Sentry/Express integration out of the box.

@tusgavomelo Sorry, for the late reply.

We have update. In our app we have a helper file with a method responsible to get a Raven instance taking into account if we are on client or server side.

let clientInstance;
let serverInstance;

const getRavenInstance = (key, config) => {
  const clientSide = typeof window !== 'undefined';

  if (clientSide) {
    if (!clientInstance) {
      const Raven = require('raven-js');  // eslint-disable-line global-require
      Raven.config(key, config).install();
      clientInstance = Raven;
    }

    return clientInstance;
  }

  if (!serverInstance) {
    // NOTE: raven (for node) is not bundled by webpack (see rules in next.config.js).
    const RavenNode = require('raven'); // eslint-disable-line global-require
    RavenNode.config(key, config).install();
    serverInstance = RavenNode;
  }
  return serverInstance;
};

This code runs both server side (when a request arrives) and client side. What we have done is configure webpack (next.config.js file) to avoid bundle the raven package.

@acanimal can you maybe provide a working example ? It seems I am not getting the full stack trace ? or maybe can you post your _error.js?

I am doing something like RavenInstance.captureException(err) in my _error.js, but I do not get to see the type of errors that occured as well as where?

export default class Error extends React.Component {
  static getInitialProps({ res, err }) {
    const RavenInstance = getRavenInstance('__SENTRY__')
    if (!(err instanceof Error)) {
      err = new Error(err && err.message)
    }
    RavenInstance.captureException(err)
    // const statusCode = res ? res.statusCode : err ? err.statusCode : null;

    return { }
  }

  render() {
    return (
      <div>
        <p>An error occurred on server</p>
      </div>
    )
  }
}

This seems like the place to ask for custom logger support since quiet must be set to false to prevent next clearing screen on module rebuild and thus purging errors from view, yet the only workable hack here requires setting quiet to false.

The errors details are also available on the stderr stream.
process.stderr.write = error => yourErrorLog(error);

Gotta love _unortodox_ solutions

function installErrorHandler(app) {
  const _renderErrorToHTML = app.renderErrorToHTML.bind(app)
  const errorHandler = rollbar.errorHandler()

  app.renderErrorToHTML = (err, req, res, pathname, query) => {
    if (err) {
      errorHandler(err, req, res, () => {})
    }

    return _renderErrorToHTML(err, req, res, pathname, query)
  }

  return app
}
// ¯\_(ツ)_/¯

Compact version of how to get correct Raven for node and browser w/o custom webpack config. Inspired by @acanimal comment

// package.json
"browser": {
    "raven": "raven-js"
}

// getRaven.js
const Raven = require('raven')

if (process.env.NODE_ENV === 'production') {
  Raven.config('YOUR_SENTRY_DSN').install()
}

module.exports = Raven

Gist

a quick recap for anyone who investigating on this problem.

// pages/_error.js
import Raven from 'raven';
...
static async getInitialProps({ store, err, isServer }) {
   if (isServer && err) {
      // https://github.com/zeit/next.js/issues/1852
      // eslint-disable-next-line global-require
      const Raven = require('raven');

      Raven.captureException(err);
    }
    ...
// next.config.js
config.plugins.push(new webpack.IgnorePlugin(/^raven$/));

thanks to @acanimal 's comment.

Install both raven (ignore with webpack as suggested in previous comments) and raven-js, then create a helper to instantiate isomorphic Raven, e.g. lib/raven.js

import Raven from 'raven-js';

// https://gist.github.com/impressiver/5092952
const clientIgnores = {
  ignoreErrors: [
    'top.GLOBALS',
    'originalCreateNotification',
    'canvas.contentDocument',
    'MyApp_RemoveAllHighlights',
    'http://tt.epicplay.com',
    "Can't find variable: ZiteReader",
    'jigsaw is not defined',
    'ComboSearch is not defined',
    'http://loading.retry.widdit.com/',
    'atomicFindClose',
    'fb_xd_fragment',
    'bmi_SafeAddOnload',
    'EBCallBackMessageReceived',
    'conduitPage',
    'Script error.',
  ],
  ignoreUrls: [
    // Facebook flakiness
    /graph\.facebook\.com/i,
    // Facebook blocked
    /connect\.facebook\.net\/en_US\/all\.js/i,
    // Woopra flakiness
    /eatdifferent\.com\.woopra-ns\.com/i,
    /static\.woopra\.com\/js\/woopra\.js/i,
    // Chrome extensions
    /extensions\//i,
    /^chrome:\/\//i,
    // Other plugins
    /127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb
    /webappstoolbarba\.texthelp\.com\//i,
    /metrics\.itunes\.apple\.com\.edgesuite\.net\//i,
  ],
};

const options = {
  autoBreadcrumbs: true,
  captureUnhandledRejections: true,
};

let IsomorphicRaven = null;

if (process.browser === true) {
  IsomorphicRaven = Raven;
  IsomorphicRaven.config(SENTRY_PUBLIC_DSN, {
    ...clientIgnores,
    ...options,
  }).install();
} else {
  // https://arunoda.me/blog/ssr-and-server-only-modules
  IsomorphicRaven = eval("require('raven')");
  IsomorphicRaven.config(
    SENTRY_DSN,
    options,
  ).install();
}

export default IsomorphicRaven;

Then you can use it in your pages/_error.js and it will work both on server and client side.

import NextError from 'next/error';
import IsomorphicRaven from 'lib/raven';

class MyError extends NextError {
  static getInitialProps = async (context) => {
    if (context.err) {
      IsomorphicRaven.captureException(context.err);
    }
    const errorInitialProps = await NextError.getInitialProps(context);
    return errorInitialProps;
  };
}

export default MyError;

Here is my PR for Rollbar sourcemap wepback plugin https://github.com/thredup/rollbar-sourcemap-webpack-plugin/pull/56 with Next.js support :)

@tusgavomelo , Can you elaborate on how to make use of the error present on the stream ?
"process.stderr.write = error => yourErrorLog(error);"

Where should we write this line of code, for it to log the error to Node console ?

That is only Client side.
Rollbar.error('some error')

https://docs.rollbar.com/docs/javascript

@teekey99 Do you have similar solution for @sentry/browser? Maybe update with-sentry example?

@sheerun I've been using raven and raven-js so far. I am aware that those will probably become deprecated as all new features are now added to @sentry/node and @sentry/browser. The thing is I haven't used these new libraries on my projects yet, but I'll try to look into it. If I have a working example I'll get back with it.

The with-sentry example was recently updated.

@timneutkens I see but it doesn't support server-side errors as Sentry is initialized inside App where it's too late to catch server errors. Proper solution would probably use @sentry/node somewhere

@sheerun I'm hitting the same issue. Using @sentry/node isn't trivial. The readme for this Sentry example suggests using a custom server, which we already have in the application I'm working on. To capture exceptions in our custom Express.js server, you need to insert a Sentry error handler middleware error as the first error handling middleware. If you insert Sentry's error handler straight after the Next handler, the error has already been swallowed by that point and Sentry doesn't see it.

I've been using @sentry/browser in the getInitialProps of _error.js and it seems to be working both client and server-side. I don't know if @sentry/browser is supposed to have server-side support, but I am getting events to Sentry.

Though I am also calling Sentry.init() via @sentry/node in a custom Express server's entry file, so perhaps that is setting some global state that the SSR is using.

Here's a gist of the setup that I'm using: https://gist.github.com/mcdougal/7bf001417c3dc4b579da224b12776691

Interesting!

There's definitely some kind of global state going on here (which is a bit scary, and probably brittle and undesirable). Applying your changes in _error.js:

  • __Without__ configuring Sentry in the custom server:

    • When running in development mode or production (i.e. with my app built), nothing is reported to Sentry when a server-side error occurs, but I get some ReferenceError: XMLHttpRequest is not defined error in the server logs

  • __After configuring__ Sentry in the custom server:

    • My server-side exceptions get reported, but I also get a weird Error: Sentry syntheticException recorded in Sentry

It would be good to understand what the official solution is. Next's docs seem to recommend using a custom <App> component now, and that's what the "with Sentry" example does, but this only works for the client side.

Error reporting for sure won't happen until first time App is rendered. Next can fail way before e.g. when rendering Page. Maybe by chance it happens to work in some cases after app has rendered on server side first time, but for sure it's not a full solution.

The with-sentry example does not work for me either @timneutkens. Running the project and testing it with my actual DSN gives out a 400 response and nothing gets to sentry (version 7 of the api).

{"error":"Bad data reconstructing object (JSONDecodeError, Expecting value: line 1 column 1 (char 0))"}

The @mcdougal did not work for me. It was already not quite great to have a global server-side setup that somehow bubbles up to the client, but even with that hack I would get sentry 301 responses.

I dont see how my sentry self-hosted setup could have a misconfiguration, as there aren't many options and Its running on multiple projects.

I'm using @sentry/browser in the same way that it is recommended in the with-sentry example, and it seems to be sending errors both from the server and the client to my sentry. I don't have any special config, only the same code as the example.

@Jauny Are you sure it is sending from the server? How have you tested that? The readme of the example even seems to suggest that it won't work on the server.

@timrogers yes indeed my bad, I assumed a tested error was coming from the server but actually was triggered from the client :(

It seems that current example with-sentry doesn't catch errors thrown in getInitialProps, just ones in render tree of application.. In my case most of the errors are from getInitialProps.

@sheerun Can you try the mcdougal workaround above? It isn't perfect (strange synthetic sentry errors) but I am under the impression it gets all errors and would like to know if that is not true. If it is true, the with-sentry example probably needs to be updated with that until Next.js can advise on how to do it better (ideally making it so the server.js sentry error handler isn't skipped?).

It seems it kinda works with two major issues:

  1. Probably because asynchronous server-side code is unnecessarily compiled into regenerator code, server-side stacktraces in production are often truncated on internal/process/next_tick.js and no cause of error is visible at all.
  2. Error handling won't work if errors are thrown before next request handler is called (e.g. in custom server code)

Actually after further testing getInitialProps of custom Error for some reason is not even firing in production when error happens for example inside custom _app's getInitialProps..

Yeah, I've definitely been getting some weird behavior after running with my attempt for a few days. Seems like the main problems we're facing are:

  1. Importing @sentry/browser for CSR and @sentry/node for SSR
  2. Finding a place to put error handling that always gets triggered for both CSR and SSR

I've been thinking of trying to use lazy imports and global state to solve #1, something like

const sentryInitialized = false;

# Inside some trigger point
const Sentry = ssr ? eval(`require('@sentry/node')`) : eval(`require('@sentry/browser')`);
if (!sentryInitialized) {
  Sentry.init({dsn: SENTRY_DSN});
}
Sentry.captureException(err);

Maybe Next's dynamic imports could be used, but I'm not that familiar with them yet. I'm also not sure the repercussions of calling require every time the error handling code gets triggered. I'll update if/when I give this a try.

As far as problem #2, seems like the possibilities are:

  1. _app.componentDidCatch, which doesn't fire for SSR
  2. _error.getInitialProps, which has a multitude of problems
  3. ...something else? Is there anything we can do for SSR in the Next server handler function?

My gut feeling is that there’ll be a way to do this on the server by injecting Sentry’s error handler before Next’s, but I haven’t given it a go yet.

At the moment, Next doesn’t provide any hooks to help you do that. If we found that this works, we could add them 👌

There’s a risk that this won’t work because Next catches too early, though.

@mcdougal Ah &#!% I was so happy when your workaround might have been good enough to check off the sentry client/server coverage required to get us into production.

Pardon my complete ignorance but do we just need next to allow us to disable its error handler conditionally so that the nodejs sentry error handler becomes the first? Some flag in next.config.js called "disableErrorHandler"?

@Enalmada I don’t think you’d want to disable the Next error handler because it’ll be that which renders a nice error page. You just want to insert other middleware before it. I think that will work, but I need to try it.

Even with that fixed, I still don’t feel like client-side error handling works as well as I’d hope :(

This whole issue is a shame and it really is a blocker to safely running Next in production.

FYI I import everywhere @sentry/node and put following into next.config.js:

        if (!isServer) {
          config.resolve.alias['@sentry/node$'] = '@sentry/browser'
        }

which can be better than eval of @mcdougal

Here are my extra notes about custom _app component and error handling:

  1. _app.js is used for ALL pages, including _error.js or pages like 404 so you really want to be sure no error is thrown when ctx.err is passed to it..
  2. Don't forget to call Component.getInitialProps in getInitialProps of app as it'll prevent getInitialProps of _error.js to be called (call it even if ctx.err is present)
class MyApp extends App {
  static async getInitialProps (appContext) {
    const { Component, ctx } = appContext
    if (ctx.err) {
      if (Component.getInitialProps) {
        pageProps = await Component.getInitialProps(ctx)
      }
      return { error: true, pageProps }
    }
    // here code that can throw an error, and then:
    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx)
    }
    return { pageProps }
  }
  render() {
    if (this.props.error) return super.render()
    // rest of code that can throw an error
  }
}

So far I find properly setting up error reporting in next.js a very fragile procedure :(

thanks @sheerun that looks like a good stop towards the right direction. I do agree that error handling in next is not optimal right now, it'll be great to see some extensible module/middleware added so we can add error handling on it etc.

The lack of isomorphic libraries such as sentry is also making things complicated, because that means we can't simple import either library in our components, we need to do it dynamically at runtime to always check if the error is raised server or browser side.

Is there an update to this issue? What i tried so far is the following: I moved all our tracking code into the _app.js

constructor(args: any) {
        super(args)
        Sentry.init({
            dsn: 'blah',
            environment: 'local',
        })
        Sentry.configureScope(scope => {
            scope.setTag('errorOrigin', isServer ? 'SSR' : 'Client')
        })
    }
    static async getInitialProps({ Component, router, ctx }: any) {
        let pageProps = {}

        try {
            if (Component.getInitialProps) {
                pageProps = await Component.getInitialProps(ctx)
            }
        } catch (error) {
            // console.log('we caught an error')
            console.log(error)
            Sentry.captureException(error)
            throw error
        }

        return { pageProps }
    }

coupled with the next.config.js addition from @sheerun and initializing sentry in the server.js too if (!isServer) { config.resolve.alias['@sentry/node$'] = '@sentry/browser' }
this seems to track all errors on the client side, but on the server side it only seems to track the first error that happens after a restart of the server. Later errors on the server are not tracked though. With this approach i do not have any SyntheticErrors in the log, but only real errors.

Still this feels quite hacky to me and since the server side tracking is only working the first time it is still non-usable.

I also added this part from the with-Sentry example

    componentDidCatch(error: any, errorInfo: any) {
        // if (process.env.FIAAS_NAMESPACE !== undefined) {
        Sentry.configureScope(scope => {
            Object.keys(errorInfo).forEach(key => {
                scope.setExtra(key, errorInfo[key])
            })
        })
        Sentry.captureException(error)
        console.log('componentDidCatch')

        // This is needed to render errors correctly in development / production
        super.componentDidCatch(error, errorInfo)
        // }
    }

but i am not totally sure if this is needed

In my case with works without issues. also you shuld not init sentry in
_app.js constructor but outside of this class entirely

On Wed, Nov 21, 2018 at 2:53 PM abraxxas notifications@github.com wrote:

Is there an update to this issue? What i tried so far is the following: I
moved all our tracking code into the _app.js

`
constructor(args: any) {
super(args)
Sentry.init({
dsn: 'blah',
environment: 'local',
})
Sentry.configureScope(scope => {
scope.setTag('errorOrigin', isServer ? 'SSR' : 'Client')
})
}

static async getInitialProps({ Component, router, ctx }: any) {
let pageProps = {}

try {
    if (Component.getInitialProps) {
        pageProps = await Component.getInitialProps(ctx)
    }
} catch (error) {
    // console.log('we caught an error')
    console.log(error)
    Sentry.captureException(error)
    throw error
}

return { pageProps }

}

`

coupled with the next.config.js addition from @sheerun
https://github.com/sheerun and initializing sentry in the server.js too if
(!isServer) { config.resolve.alias['@sentry/node$'] = '@sentry/browser' }
this seems to track all errors on the client side, but on the server side
it only seems to track the first error that happens after a restart of the
server. Later errors on the server are not tracked though. With this
approach i do not have any SyntheticErrors in the log, but only real errors.

Still this feels quite hacky to me and since the server side tracking is
only working the first time it is still non-usable.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/zeit/next.js/issues/1852#issuecomment-440668980, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAR2DeIhoOj6PdWRA2VqiEZyrO5Jui8vks5uxVrHgaJpZM4NOQlp
.

I tried moving it out already and still the same behaviour. @sheerun could you maybe post a minimal gist of your setup? I tried setting it up with the snippets you provided and i just can't get it to work. The whole thing seems overly complicated for what i expected to be a rather simple task :( Are you also initializing sentry on the server or only in _app.js outside the class?

I'd update official sentry example but I'm afraid it'll be rejected as "too complex" I can try to post something though when I'll find time..

@sheerun Even an attempt to update to the official example would be of great value. I feel it would get merged if it really is the minimum complexity necessary to get sentry ssr working without SyntheticErrors or only recording the first server error that happens. Then we can go from there to figure out ways of making it better or pushing for nextjs core improvements or sentry isomorphic support.

So now that we have a necessarily complex working example, what are the next steps to improve the situation:

The only thing one needs in next.js is ability to add custom middleware in next.config.js and something like next.browser.js for universal (isomorphic) plugin configuration (next.config.js is used among others for webpack configuration, which means other things defined in this file cannot be used in application code, because it would cause circular dependency).

For next.browser.js next.js could define configuration like decorator for App or Document components. This way I could implement sentry integration entirely as a plugin.

EDIT: I don't know whether there is ticket for this, but I think @timneutkens is already extracting next server into separate packages. I think best plugin interface would be something like:

module.exports = {
  server: server => {
    server.use(Sentry.Handlers.errorHandler())
  }
}

I've implemented proposal of such API in #6922

It allows to add decorators to custom server code because contract is changed to one that doesn't automatically call .listen() so it allows next.js to decorate it further before instantiating

I was having a lot of trouble using the with-sentry example, so I opened up a PR for a much more simple example. It doesn't have all the bells and whistles, but it's been working for me.

https://github.com/zeit/next.js/pull/7119

Try this in custom _document.js:

import React from "react";
import Document, {
  Html,
  Head,
  Main,
  NextScript,
} from "next/document";
import { NodeClient } from "@sentry/node";

const { default: getConfig } = require("next/config");
const { publicRuntimeConfig: { sentryDSN } } = getConfig();

let sentry = null;
if (sentryDSN) {
  sentry = new NodeClient({ dsn: sentryDSN });
}

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    if (ctx.err && sentry) sentry.captureException(ctx.err);
    return { ...initialProps };
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

And this in custom _app.js:

import * as Sentry from "@sentry/browser";

const { default: getConfig } = require("next/config");
const { publicRuntimeConfig: { sentryDSN } } = getConfig();

if (sentryDSN) {
  Sentry.init({ dsn: sentryDSN });
}

It helps me.

Tbh i am very confused on what the correct places to catch errors for next.js are right now. There are _app.tsx, _error.tsx and _document.tsx there are some comments saying that some stuff should be caught in _error.tsx (like: https://github.com/zeit/next.js/pull/5727/files#r235981700) but the current examples use either _app.tsx or _app and _document.

I tried both ways now and it seems that some errors that happen during ssr are not getting caught for me. Which places are now the correct ones to check for errors? The most logical one for me seems to check in the componentDidCatch of _app and the getInitialProps of the _error as this one is being served in cases of errors. I am a bit confused about the whole process.on part in the _document

Could someone with more in depth knowledge please resolve this question?

@abraxxas process.on in _document.js is catching errors that happen on the server. As a recap,

  • _document.js is _server-side only_ and is used to change the initial server-side rendered document markup.
  • _app.js is _client-side only_ and is used to initialize pages.

Therefore, when an error happens on the server it should throw an error to Sentry via process.on and then the client will render the default error page or _error.js.

Hopefully this will help: https://leerob.io/blog/configuring-sentry-for-nextjs-apps/

_app.js is not client-side only, however componentDidCatch is

@timneutkens Okay, that wrinkles my brain a little bit. I think the docs have been updated since the last time I looked. Could you explain how _app.js is not client-side only in more detail?

@timneutkens could you maybe explain where and why you would catch errors? There seem to be so many opinions.

@timneutkens Okay, that wrinkles my brain a little bit. I think the docs have been updated since the last time I looked. Could you explain how _app.js is not client-side only in more detail?

Hello leerob!
I just posted a question on the merge request of your example:
https://github.com/zeit/next.js/pull/7360#issuecomment-514318899

Adding to that question... I also tried to throw an error in render() on the server side (i started the state with raiseErrorInRender: true, so the first render, that is server sided, would already throw an error), and that error also wasn't captured by Sentry.

Have you tested that in your application, and are those server sided errors really being captured? I'm using Next 9.

If that really is the case, and only the client side errors are being captured, there would also be no reason to override the _document.js file.

Thanks in advance for any help!

@timneutkens Okay, that wrinkles my brain a little bit. I think the docs have been updated since the last time I looked. Could you explain how _app.js is not client-side only in more detail?

Answering your question, _app is where an App component is defined to initialize pages. We would override it in cases like the need for a persisting layout between page changes (all pages utilize the same _app), or to make use of redux. So, even the first render, that occurs on server side, will render the content of _app (it is not client side only).

Adding to that question... I also tried to throw an error in render() on the server side (i started the state with raiseErrorInRender: true, so the first render, that is server sided, would already throw an error), and that error also wasn't captured by Sentry.

Have you managed in any other way to get that error to show up in sentry? I tried capturing it in the getInitialProps of _error but that also does show nothing in sentry. Currently i haven't found one reliable way to capture errors on the server, some of them (mostly if they are connected to api failures) they show up but i have not managed to get a simple error from the errortestpage to show up in sentry

Adding to that question... I also tried to throw an error in render() on the server side (i started the state with raiseErrorInRender: true, so the first render, that is server sided, would already throw an error), and that error also wasn't captured by Sentry.

Have you managed in any other way to get that error to show up in sentry? I tried capturing it in the getInitialProps of _error but that also does show nothing in sentry. Currently i haven't found one reliable way to capture errors on the server, some of them (mostly if they are connected to api failures) they show up but i have not managed to get a simple error from the errortestpage to show up in sentry

Unfortunately not. The solution i think i'm going to implement is to use the example (minus the overrided _document.js file) as my template to capture errors that occur on the client side, since those are the ones that i have no control and way to know about, unless users report then to me. On the server side, i think i wil just redirect any log line directly to a log file. That is definitely not the best solution since i wanted to have all errors on the same place, but doing it that way i will at least have information about any error that might occur on the application.

What do you think of that? Thanks!

Adding to that question... I also tried to throw an error in render() on the server side (i started the state with raiseErrorInRender: true, so the first render, that is server sided, would already throw an error), and that error also wasn't captured by Sentry.

Have you managed in any other way to get that error to show up in sentry? I tried capturing it in the getInitialProps of _error but that also does show nothing in sentry. Currently i haven't found one reliable way to capture errors on the server, some of them (mostly if they are connected to api failures) they show up but i have not managed to get a simple error from the errortestpage to show up in sentry

Unfortunately not. The solution i think i'm going to implement is to use the example (minus the overrided _document.js file) as my template to capture errors that occur on the client side, since those are the ones that i have no control and way to know about, unless users report then to me. On the server side, i think i wil just redirect any log line directly to a log file. That is definitely not the best solution since i wanted to have all errors on the same place, but doing it that way i will at least have information about any error that might occur on the application.

What do you think of that? Thanks!

That is exactly what we are doing right now, it's a bit clunky but the best we could come up. But since some server-side errors show up in sentry for us there must be something going on either with sentry or next.js i think. I tried with both the simple and the more complex example and both of them behave the same so i am at least somewhat confident that this behaviour is not related to our setup

Folks in this thread might be interested in https://github.com/zeit/next.js/pull/8684 and related bugs. It has 12 different tests of unhandled exceptions that you can play with to gain an understanding of what exceptions Next.js handles for you and what it doesn't.

Any news regarding this issue?

My solution is use redux save the node fetch data error in state。Then in the componentDidMount of _app.js do something(alert for user or post error req to backend).
The code is in there next-antd-scaffold_server-error.

========> state

import {
  SERVER_ERROR,
  CLEAR_SERVER_ERROR
} from '../../constants/ActionTypes';

const initialState = {
  errorType: []
};

const serverError = (state = initialState, { type, payload }) => {
  switch (type) {
    case SERVER_ERROR: {
      const { errorType } = state;
      errorType.includes(payload) ? null : errorType.push(payload);
      return {
        ...state,
        errorType
      };
    }
    case CLEAR_SERVER_ERROR: {
      return initialState;
    }
    default:
      return state;
  }
};

export default serverError;

=======> action

import {
  SERVER_ERROR
} from '../../constants/ActionTypes';

export default () => next => action => {
  if (!process.browser && action.type.includes('FAIL')) {
    next({
      type: SERVER_ERROR,
      payload: action.type 
    });
  }
  return next(action);
};

=======> _app.js

...
componentDidMount() {
    const { store: { getState, dispatch } } = this.props;
    const { errorType } = getState().serverError;
    if (errorType.length > 0) {
      Promise.all(
        errorType.map(type => message.error(`Node Error, Code:${type}`))
      );
      dispatch(clearServerError());
    }
  }
...

Gotta love _unortodox_ solutions

function installErrorHandler(app) {
  const _renderErrorToHTML = app.renderErrorToHTML.bind(app)
  const errorHandler = rollbar.errorHandler()

  app.renderErrorToHTML = (err, req, res, pathname, query) => {
    if (err) {
      errorHandler(err, req, res, () => {})
    }

    return _renderErrorToHTML(err, req, res, pathname, query)
  }

  return app
}
// ¯\_(ツ)_/¯

This method cannot be applied to 404 error because the err argument is going to be null when 404 error.

I solved this for the Elastic APM node client (https://www.npmjs.com/package/elastic-apm-node)

For anyone interested:

In next.config.js:

webpack: (config, { isServer, webpack }) => {
      if (!isServer) {
        config.node = {
          dgram: 'empty',
          fs: 'empty',
          net: 'empty',
          tls: 'empty',
          child_process: 'empty',
        };

        // ignore apm (might use in nextjs code but dont want it in client bundles)
        config.plugins.push(
          new webpack.IgnorePlugin(/^(elastic-apm-node)$/),
        );
      }

      return config;
} 

Then in _error.js render func you can call captureError manually:

function Error({ statusCode, message, err }) {

const serverSide = typeof window === 'undefined';

  // only run this on server side as APM only works on server
  if (serverSide) {
    // my apm instance (imports elastic-apm-node and returns  captureError)
    const { captureError } = require('../src/apm'); 

    if (err) {
      captureError(err);
    } else {
      captureError(`Message: ${message}, Status Code: ${statusCode}`);
    }
  }

}

Hey all! We just launched an NPM library for Express style architecture in Next.js without adding an Express server. It might be helpful for your server error handling challenges! Check it out if you're interested. https://github.com/oslabs-beta/connext-js

Anyone had success catching (and handle) exceptions that occur in getServerSideProps?

Anyone had success catching (and handle) exceptions that occur in getServerSideProps?

It's unclear, you'd think a framework would provide an idiomatic way to log errors both client and server 🤷‍♂️

Anyone had success catching (and handle) exceptions that occur in getServerSideProps?

@stephankaag

yes, something like this worked for me:

first create a middleware to handle the crash reporting. I've wrapped Sentry inside the CrashReporter class because simply returning Sentry will not work (i.e. req.getCrashReporter = () => Sentry).

// crash-reporter.js

const Sentry = require("@sentry/node");

class CrashReporter {
  constructor(){
    Sentry.init({ dsn: process.env.SENTRY_DSN });
  }
  captureException(ex){
    return Sentry.captureException(ex);
  }
}

function crashReporterMiddleware(req, res, next) {
  req.getCrashReporter = () => new CrashReporter();
  next();
}

module.exports = crashReporterMiddleware;

next, of course, load the middleware into your app before the Next.js request handler is set:

// server.js

const crashReporterMiddleware = require("./middleware/crash-reporter")

...

app.use(crashReporterMiddleware);

...

setHandler((req, res) => {
  return handle(req, res);
});

then wherever you call getServerSideProps:

// _error.js

export async function getServerSideProps({req, res, err}) {
  const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
  const crashReporter = req.getCrashReporter();
  const eventId = crashReporter.captureException(err);
  req.session.eventId = eventId;
  return {
    props: { statusCode, eventId }
  }
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

flybayer picture flybayer  ·  3Comments

lixiaoyan picture lixiaoyan  ·  3Comments

havefive picture havefive  ·  3Comments

knipferrc picture knipferrc  ·  3Comments

ghost picture ghost  ·  3Comments