src/pages/_document.tsx
import * as Sentry from '@sentry/node';
import universalLanguageDetect from '@unly/universal-language-detector';
import { ERROR_LEVELS } from '@unly/universal-language-detector/lib/utils/error';
import { isBrowser } from '@unly/utils';
import { createLogger } from '@unly/utils-simple-logger';
import classnames from 'classnames';
import get from 'lodash.get';
import { NextPageContext } from 'next';
import NextCookies from 'next-cookies';
import Document, { DocumentContext, DocumentProps, Head, Main, NextScript } from 'next/document';
import React from 'react';
import { Cookies } from '../types/Cookies';
import { DocumentInitialProps } from '../types/DocumentInitialProps';
import { LANG_EN, SUPPORTED_LANGUAGES } from '../utils/i18n';
const fileLabel = 'pages/_document';
const logger = createLogger({
label: fileLabel,
});
/**
* Send to Sentry all unhandled rejections.
*
* If such error happens in this file, it will completely crash the server and render "Internal Server Error" on the client.
* @see https://leerob.io/blog/configuring-sentry-for-nextjs-apps
*/
process.on('unhandledRejection', (e: Error): void => {
Sentry.captureException(e);
});
/**
* Send to Sentry all uncaught exceptions.
*
* If such error happens in this file, it will completely crash the server and render "Internal Server Error" on the client.
* @see https://leerob.io/blog/configuring-sentry-for-nextjs-apps
*/
process.on('uncaughtException', (e: Error): void => {
Sentry.captureException(e);
});
/**
* XXX Is only rendered on the server side and not on the client side
*
* Used to inject <html lang=""> tag
*
* See https://github.com/zeit/next.js/#custom-document
*/
class NRNDocument extends Document<Props> {
static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
try {
Sentry.addBreadcrumb({ // See https://docs.sentry.io/enriching-error-data/breadcrumbs
category: fileLabel,
message: `Preparing document (${isBrowser() ? 'browser' : 'server'})`,
level: Sentry.Severity.Debug,
});
const initialProps: DocumentInitialProps = await Document.getInitialProps(ctx);
const { req }: NextPageContext = ctx;
const cookies: Cookies = NextCookies(ctx); // Parses Next.js cookies in a universal way (server + client)
const lang: string = universalLanguageDetect({
supportedLanguages: SUPPORTED_LANGUAGES, // Whitelist of supported languages, will be used to filter out languages that aren't supported
fallbackLanguage: LANG_EN, // Fallback language in case the user's language cannot be resolved
acceptLanguageHeader: get(req, 'headers.accept-language'), // Optional - Accept-language header will be used when resolving the language on the server side
serverCookies: cookies, // Optional - Cookie "i18next" takes precedence over navigator configuration (ex: "i18next: fr"), will only be used on the server side
errorHandler: (error: Error, level: ERROR_LEVELS, origin: string, context: object): void => {
Sentry.withScope((scope): void => {
scope.setExtra('level', level);
scope.setExtra('origin', origin);
scope.setContext('context', context);
Sentry.captureException(error);
});
logger.error(error.message);
},
});
return { ...initialProps, lang };
} catch (e) {
// If an error happens, log it and then try to render the page again with minimal processing
// This way, it'll render something on the client. (otherwise, it'd completely crash the server and render "Internal Server Error" on the client)
Sentry.captureException(e);
const initialProps: DocumentInitialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
}
render(): JSX.Element {
const { lang }: DocumentProps & { lang: string } = this.props;
return (
<html lang={this.props.lang}>
<Head />
<body className={classnames('nrn', `lang-${lang}`)}>
<Main />
<NextScript />
</body>
</html>
);
}
}
type Props = {
lang: string;
} & DocumentProps
export default NRNDocument;