next.config.js
const bundleAnalyzer = require('@next/bundle-analyzer');
const nextSourceMaps = require('@zeit/next-source-maps');
const packageJson = require('./package');
const withSourceMaps = nextSourceMaps();
const withBundleAnalyzer = bundleAnalyzer({ // Run with "yarn analyse:bundle" - See https://www.npmjs.com/package/@next/bundle-analyzer
enabled: process.env.ANALYZE_BUNDLE === 'true',
});
const date = new Date();
const GIT_COMMIT_SHA_SHORT = typeof process.env.GIT_COMMIT_SHA === 'string' && process.env.GIT_COMMIT_SHA.substring(0, 8);
console.debug(`Building Next with NODE_ENV="${process.env.NODE_ENV}" NEXT_PUBLIC_APP_STAGE="${process.env.NEXT_PUBLIC_APP_STAGE}" using GIT_COMMIT_SHA=${process.env.GIT_COMMIT_SHA} and GIT_COMMIT_REF=${process.env.GIT_COMMIT_REF}`);
const GIT_COMMIT_TAGS = (process.env.GIT_COMMIT_TAGS ? process.env.GIT_COMMIT_TAGS.trim() : '').replace('refs/tags/', '');
console.debug(`Deployment will be tagged automatically, using GIT_COMMIT_TAGS: "${GIT_COMMIT_TAGS}"`);
// Iterate over all tags and extract the first the match "v*"
const APP_RELEASE_TAG = GIT_COMMIT_TAGS ? GIT_COMMIT_TAGS.split(' ').find((tag) => tag.startsWith('v')) : `unknown-${GIT_COMMIT_SHA_SHORT}`;
console.debug(`Release version resolved from tags: "${APP_RELEASE_TAG}" (matching first tag starting with "v")`);
/**
* This file is for advanced configuration of the Next.js framework.
*
* The below config applies to the whole application.
* next.config.js gets used by the Next.js server and build phases, and it's not included in the browser build.
*
* XXX Not all configuration options are listed below, we only kept those of most interest.
* You'll need to dive into Next.js own documentation to find out about what's not included.
* Basically, we focused on options that seemed important for a SSG/SSR app running on serverless mode (Vercel).
* Also, we included some options by are not using them, this is mostly to help make you aware of those options, in case you'd need them.
*
* @see https://nextjs.org/docs/api-reference/next.config.js/introduction
*/
module.exports = withBundleAnalyzer(withSourceMaps({
// basepath: '', // If you want Next.js to cover only a subsection of the domain. See https://nextjs.org/docs/api-reference/next.config.js/basepath
// target: 'serverless', // Automatically enabled on Vercel, you may need to manually opt-in if you're not using Vercel. See https://nextjs.org/docs/api-reference/next.config.js/build-target#serverless-target
// trailingSlash: false, // By default Next.js will redirect urls with trailing slashes to their counterpart without a trailing slash. See https://nextjs.org/docs/api-reference/next.config.js/trailing-slash
/**
* React's Strict Mode is a development mode only feature for highlighting potential problems in an application.
* It helps to identify unsafe lifecycles, legacy API usage, and a number of other features.
*
* Officially suggested by Next.js:
* We strongly suggest you enable Strict Mode in your Next.js application to better prepare your application for the future of React.
*
* If you or your team are not ready to use Strict Mode in your entire application, that's OK! You can incrementally migrate on a page-by-page basis using <React.StrictMode>.
*
* @see https://nextjs.org/docs/api-reference/next.config.js/react-strict-mode
*/
reactStrictMode: true,
// TODO doc
images: {
domains: [],
},
/**
* Environment variables added to JS bundle
*
* XXX All env variables defined in ".env*" files that aren't public (those that don't start with "NEXT_PUBLIC_") MUST manually be made available at build time below.
* They're necessary on Vercel for runtime execution (SSR, SSG with revalidate, everything that happens server-side will need those).
*
* XXX This is a duplication of the environment variables.
* The variables defined below are only used locally, while those in "vercel.*.json:build:env" will be used on the Vercel platform.
* See https://vercel.com/docs/v2/build-step/#providing-environment-variables
*
* @see https://nextjs.org/docs/api-reference/next.config.js/environment-variables
*/
env: {
GITHUB_DISPATCH_TOKEN: process.env.GITHUB_DISPATCH_TOKEN,
SENTRY_DSN: process.env.SENTRY_DSN,
// Vercel env variables - See https://vercel.com/docs/environment-variables#system-environment-variables
VERCEL: process.env.VERCEL,
VERCEL_ENV: process.env.VERCEL_ENV,
VERCEL_URL: process.env.VERCEL_URL,
CI: process.env.CI,
// Dynamic env variables
NEXT_PUBLIC_APP_DOMAIN: process.env.VERCEL_URL, // Alias
NEXT_PUBLIC_APP_BASE_URL: process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : 'http://localhost:9999',
NEXT_PUBLIC_APP_BUILD_TIME: date.toString(),
NEXT_PUBLIC_APP_BUILD_TIMESTAMP: +date,
NEXT_PUBLIC_APP_NAME: packageJson.name,
NEXT_PUBLIC_APP_NAME_VERSION: `${packageJson.name}-${APP_RELEASE_TAG}`,
GIT_COMMIT_SHA_SHORT,
GIT_COMMIT_SHA: process.env.GIT_COMMIT_SHA, // Resolve commit hash from ENV first (set through CI), fallbacks to reading git (when used locally, through "/scripts/populate-git-env.sh")
GIT_COMMIT_REF: process.env.GIT_COMMIT_REF, // Resolve commit ref (branch/tag) from ENV first (set through CI), fallbacks to reading git (when used locally, through "/scripts/populate-git-env.sh")
GIT_COMMIT_TAGS: process.env.GIT_COMMIT_TAGS || '', // Resolve commit tags/releases from ENV first (set through CI), fallbacks to reading git (when used locally, through "/scripts/populate-git-env.sh")
},
/**
* Headers allow you to set custom HTTP headers for an incoming request path.
*
* Headers allow you to set route specific headers like CORS headers, content-types, and any other headers that may be needed.
* They are applied at the very top of the routes.
*
* @example source: '/(.*?)', // Match all paths, including "/"
* @example source: '/:path*', // Match all paths, excluding "/"
*
* @return {Promise<Array<{ headers: [{value: string, key: string}], source: string }>>}
* @see https://nextjs.org/docs/api-reference/next.config.js/headers
* @since 9.5 - See https://nextjs.org/blog/next-9-5#headers
*/
async headers() {
// Disabling embedding by default is safer.
const DISABLE_IFRAME_EMBED_FROM_3RD_PARTIES = true;
const headers = [
{
// Make all fonts immutable and cached for one year
'source': '/static/fonts/(.*?)',
'headers': [
{
'key': 'Cache-Control',
// See https://www.keycdn.com/blog/cache-control-immutable#what-is-cache-control-immutable
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#browser_compatibility
'value': `public, max-age=31536000, immutable`,
},
],
},
{
// Make all other static assets immutable and cached for one hour
'source': '/static/(.*?)',
'headers': [
{
'key': 'Cache-Control',
// See https://www.keycdn.com/blog/cache-control-immutable#what-is-cache-control-immutable
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#browser_compatibility
'value': `public, max-age=3600, immutable`,
},
],
},
{
source: '/(.*?)', // Match all paths, including "/" - See https://github.com/vercel/next.js/discussions/17991#discussioncomment-112028
headers: [
// This directive helps protect against some XSS attacks
// See https://infosec.mozilla.org/guidelines/web_security#x-content-type-options
{
key: 'X-Content-Type-Options',
value: `nosniff`,
},
],
},
{
source: '/(.*?)', // Match all paths, including "/" - See https://github.com/vercel/next.js/discussions/17991#discussioncomment-112028
headers: [
// This directive helps protect user's privacy and might avoid leaking sensitive data in urls to 3rd parties (e.g: when loading a 3rd party asset)
// See https://infosec.mozilla.org/guidelines/web_security#referrer-policy
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
// See https://scotthelme.co.uk/a-new-security-header-referrer-policy/
{
key: 'Referrer-Policy',
// "no-referrer-when-downgrade" is the default behaviour
// XXX You might want to restrict even more the referrer policy
value: `no-referrer-when-downgrade`,
},
],
},
{
source: '/(.*?)', // Match all paths, including "/" - See https://github.com/vercel/next.js/discussions/17991#discussioncomment-112028
// source: '/:path*', // Match all paths, excluding "/"
headers: [
{
key: 'X-Content-Type-Options',
value: `nosniff`,
},
],
},
];
// When 3rd party embeds are forbidden, only allow same origin to embed iframe by default
// This is a stronger default, if you don't to embed your site in any external website
if (DISABLE_IFRAME_EMBED_FROM_3RD_PARTIES) {
headers.push({
source: '/(.*?)', // Match all paths, including "/" - See https://github.com/vercel/next.js/discussions/17991#discussioncomment-112028
headers: [
{
// This directive's "ALLOW-FROM" option is deprecated in favor of "Content-Security-Policy" "frame-ancestors"
// So, we use a combination of both the CSP directive and the "X-Frame-Options" for browser that don't support CSP
// See https://infosec.mozilla.org/guidelines/web_security#x-frame-options
key: 'X-Frame-Options',
value: `SAMEORIGIN`,
},
],
});
headers.push({
source: '/(.*?)', // Match all paths, including "/" - See https://github.com/vercel/next.js/discussions/17991#discussioncomment-112028
// See https://infosec.mozilla.org/guidelines/web_security#x-frame-options
headers: [
{
key: 'Content-Security-Policy',
value: `frame-ancestors 'self`,
},
],
});
}
console.info('Using headers:', JSON.stringify(headers, null, 2));
return headers;
},
/**
* Rewrites allow you to map an incoming request path to a different destination path.
*
* Rewrites are only available on the Node.js environment and do not affect client-side routing.
* Rewrites are the most commonly used form of custom routing — they're used for dynamic routes (pretty URLs), user-land routing libraries (e.g. next-routes), internationalization, and other advanced use cases.
*
* For example, the route /user/:id rendering a specific user's profile page is a rewrite.
* Rendering your company's about page for both /about and /fr/a-propos is also a rewrite.
* The destination url can be internal, or external.
*
* @return { Promise<Array<{ destination: string, source: string, headers: Array<{ key: string, value: string }> }>> }
* @see https://nextjs.org/docs/api-reference/next.config.js/rewrites
* @since 9.5 - See https://nextjs.org/blog/next-9-5#rewrites
*/
async rewrites() {
const rewrites = [
// Robots rewrites
{
source: `/robots.txt`,
destination: process.env.NEXT_PUBLIC_APP_STAGE === 'production' ? `/robots/production.txt` : '/robots/!production.txt',
},
];
console.info('Using rewrites:', rewrites);
return rewrites;
},
/**
* Redirects allow you to redirect an incoming request path to a different destination path.
*
* Redirects are only available on the Node.js environment and do not affect client-side routing.
* By redirects, we mean HTTP Redirects (aka URL forwarding).
* Redirects are most commonly used when a website is reorganized — ensuring search engines and bookmarks are forwarded to their new locations.
* The destination url can be internal, or external.
*
* @return { Promise<Array<{ permanent: boolean, destination: string, source: string, statusCode?: number }>> }
* @see https://nextjs.org/docs/api-reference/next.config.js/redirects
* @since 9.5 - See https://nextjs.org/blog/next-9-5#redirects
*/
async redirects() {
const redirects = [];
console.info('Using redirects:', redirects);
return redirects;
},
// See https://nextjs.org/docs/messages/webpack5
// Necessary to manually specify to use webpack 5, because we use a custom "webpack" config (see below)
webpack5: true,
resolve: {
fallback: {
// Fixes npm packages that depend on `fs` module
fs: false,
},
},
/**
* The webpack function is executed twice, once for the server and once for the client.
* This allows you to distinguish between client and server configuration using the isServer property.
*
* @param config Current webpack config. Useful to reuse parts of what's already configured while overridding other parts.
* @param buildId The build id, used as a unique identifier between builds.
* @param dev Indicates if the compilation will be done in development.
* @param isServer It's true for server-side compilation, and false for client-side compilation.
* @param defaultLoaders Default loaders used internally by Next.js:
* - babel Default babel-loader configuration
* @see https://nextjs.org/docs/api-reference/next.config.js/custom-webpack-config
*/
webpack: (config, {
buildId,
dev,
isServer,
defaultLoaders,
}) => {
if (isServer) {
/**
* This special server-only environment variable isn't string-replaced by webpack during bundling (it isn't added to the DefinePlugin definitions).
*
* Therefore, it's:
* - Always '1' on the server, during development
* - Always '1' on the server, during the Next.js build step
* - Always undefined on the browser
* - Always undefined in API endpoints
* - Always undefined during static pages re-generations (ISG) and server-side pages
*
* It can be useful when performing processing that should only happen during the initial build, or not during the initial build.
*/
process.env.IS_SERVER_INITIAL_BUILD = '1';
}
const APP_VERSION_RELEASE = APP_RELEASE_TAG || buildId;
config.plugins.map((plugin, i) => {
// Inject custom environment variables in "DefinePlugin" - See https://webpack.js.org/plugins/define-plugin/
if (plugin.__proto__.constructor.name === 'DefinePlugin') {
// Dynamically add some "public env" variables that will be replaced during the build through "DefinePlugin"
// Those variables are considered public because they are available at build time and at run time (they'll be replaced during initial build, by their value)
plugin.definitions['process.env.NEXT_PUBLIC_APP_BUILD_ID'] = JSON.stringify(buildId);
plugin.definitions['process.env.NEXT_PUBLIC_APP_VERSION_RELEASE'] = JSON.stringify(APP_VERSION_RELEASE);
}
});
if (isServer) { // Trick to only log once
console.debug(`[webpack] Building release "${APP_VERSION_RELEASE}" using NODE_ENV="${process.env.NODE_ENV}" ${process.env.IS_SERVER_INITIAL_BUILD ? 'with IS_SERVER_INITIAL_BUILD="1"' : ''}`);
}
// XXX See https://github.com/vercel/next.js/blob/canary/examples/with-sentry-simple/next.config.js
// In `pages/_app.js`, Sentry is imported from @sentry/node. While
// @sentry/browser will run in a Node.js environment, @sentry/node will use
// Node.js-only APIs to catch even more unhandled exceptions.
//
// This works well when Next.js is SSRing your page on a server with
// Node.js, but it is not what we want when your client-side bundle is being
// executed by a browser.
//
// Luckily, Next.js will call this webpack function twice, once for the
// server and once for the client. Read more:
// https://nextjs.org/docs#customizing-webpack-config
//
// So ask Webpack to replace @sentry/node imports with @sentry/browser when
// building the browser's bundle
if (!isServer) {
config.resolve.alias['@sentry/node'] = '@sentry/browser';
}
return config;
},
/**
* Next.js uses a constant id generated at build time to identify which version of your application is being served.
*
* This can cause problems in multi-server deployments when next build is ran on every server.
* In order to keep a static build id between builds you can provide your own build id.
*
* XXX We documented this function in case you might want to use it, but we aren't using it.
*
* @see https://nextjs.org/docs/api-reference/next.config.js/configuring-the-build-id
*/
// generateBuildId: async () => {
// // You can, for example, get the latest git commit hash here
// return 'my-build-id'
// },
/**
* Next.js exposes some options that give you some control over how the server will dispose or keep in memory built pages in development.
*
* XXX We documented this function in case you might want to use it, but we aren't using it.
*
* @see https://nextjs.org/docs/api-reference/next.config.js/configuring-onDemandEntries
*/
// onDemandEntries: {
// // period (in ms) where the server will keep pages in the buffer
// maxInactiveAge: 25 * 1000,
// // number of pages that should be kept simultaneously without being disposed
// pagesBufferLength: 2,
// },
poweredByHeader: false, // See https://nextjs.org/docs/api-reference/next.config.js/disabling-x-powered-by
}));