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?
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).
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:
catch
block in client/index.js for handling this kind of error?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
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')
@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
:
ReferenceError: XMLHttpRequest is not defined
error in the server logsError: Sentry syntheticException
recorded in SentryIt 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:
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:
@sentry/browser
for CSR and @sentry/node
for SSRI'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:
_app.componentDidCatch
, which doesn't fire for SSR_error.getInitialProps
, which has a multitude of problemsMy 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:
_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.._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.
Here it goes.. https://github.com/zeit/next.js/pull/5727
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.
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());
}
}
...
Thanks to https://github.com/zeit/next.js/issues/1852#issuecomment-353671222 I got an example working with server-side Rollbar error reporting.
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 }
}
}
Most helpful comment
Gotta love _unortodox_ solutions