diff --git a/next.config.js b/next.config.js index 8727dc7f7..734346947 100644 --- a/next.config.js +++ b/next.config.js @@ -17,12 +17,46 @@ const date = new Date(); console.debug(`Building Next with NODE_ENV="${process.env.NODE_ENV}" NEXT_PUBLIC_APP_STAGE="${process.env.NEXT_PUBLIC_APP_STAGE}" for NEXT_PUBLIC_CUSTOMER_REF="${process.env.NEXT_PUBLIC_CUSTOMER_REF}"`); +/** + * 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({ - // 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 + // 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 . + * + * @see https://nextjs.org/docs/api-reference/next.config.js/react-strict-mode + */ + // reactStrictMode: true, // XXX Disabled for now, but we should enable it + + /** + * 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). + * + * @see https://nextjs.org/docs/api-reference/next.config.js/environment-variables + */ env: { - // XXX All env variables defined in ".env*" files that aren't public (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) - // See https://nextjs.org/docs/api-reference/next.config.js/environment-variables AIRTABLE_API_KEY: process.env.AIRTABLE_API_KEY, AIRTABLE_BASE_ID: process.env.AIRTABLE_BASE_ID, LOCIZE_API_KEY: process.env.LOCIZE_API_KEY, @@ -35,50 +69,112 @@ module.exports = withBundleAnalyzer(withSourceMaps({ NEXT_PUBLIC_APP_VERSION: packageJson.version, UNLY_SIMPLE_LOGGER_ENV: process.env.NEXT_PUBLIC_APP_STAGE, // Used by @unly/utils-simple-logger - Fix missing staging logs because otherwise it believes we're in production }, - experimental: { - redirects() { - const redirects = [ - { - // Redirect root link with trailing slash to non-trailing slash, avoids 404 - See https://github.com/vercel/next.js/discussions/10651#discussioncomment-8270 - source: '/:locale/', - destination: '/:locale', - permanent: process.env.NEXT_PUBLIC_APP_STAGE !== 'development', // Do not use permanent redirect locally to avoid browser caching when working on it - }, - { - // Redirect link with trailing slash to non-trailing slash (any depth), avoids 404 - See https://github.com/vercel/next.js/discussions/10651#discussioncomment-8270 - source: '/:locale/:path*/', - destination: '/:locale/:path*', - permanent: process.env.NEXT_PUBLIC_APP_STAGE !== 'development', // Do not use permanent redirect locally to avoid browser caching when working on it - }, - ]; - - if (process.env.NEXT_PUBLIC_APP_STAGE === 'development') { - console.info('Using experimental redirects:', redirects); - } - return redirects; - }, - rewrites() { - const rewrites = [ - { - // XXX Doesn't work locally (maybe because of rewrites), but works online - source: '/', - destination: '/api/autoRedirectToLocalisedPage', - }, - { - source: `/:locale((?!${noRedirectBasePaths.join('|')})[^/]+)(.*)`, - destination: '/api/autoRedirectToLocalisedPage', - }, - ]; - - if (process.env.NEXT_PUBLIC_APP_STAGE === 'development') { - console.info('Using experimental rewrites:', rewrites); - } + /** + * 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. + * + * @return {Promise>} + * @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() { + const headers = []; + + console.info('Using headers:', headers); + + 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 }>> } + * @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 = [ + // I18n rewrites + { + // XXX Doesn't work locally (maybe because of rewrites), but works online + source: '/', + destination: '/api/autoRedirectToLocalisedPage', + }, + { + source: `/:locale((?!${noRedirectBasePaths.join('|')})[^/]+)(.*)`, + destination: '/api/autoRedirectToLocalisedPage', + }, + + // Robots rewrites + { + source: `/robots.txt`, + destination: process.env.NEXT_PUBLIC_APP_STAGE === 'production' ? `/robots/production.txt` : '/robots/!production.txt', + }, + ]; - return rewrites; - }, + 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> } + * @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 = [ + // I18n redirects + { + // Redirect root link with trailing slash to non-trailing slash, avoids 404 - See https://github.com/vercel/next.js/discussions/10651#discussioncomment-8270 + source: '/:locale/', + destination: '/:locale', + permanent: process.env.NEXT_PUBLIC_APP_STAGE !== 'development', // Do not use permanent redirect locally to avoid browser caching when working on it + }, + { + // Redirect link with trailing slash to non-trailing slash (any depth), avoids 404 - See https://github.com/vercel/next.js/discussions/10651#discussioncomment-8270 + source: '/:locale/:path*/', + destination: '/:locale/:path*', + permanent: process.env.NEXT_PUBLIC_APP_STAGE !== 'development', // Do not use permanent redirect locally to avoid browser caching when working on it + }, + ]; + + console.info('Using redirects:', redirects); + + return redirects; }, - webpack: (config, { isServer, buildId }) => { + + /** + * + * 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) { // IS_SERVER_INITIAL_BUILD is meant to be defined only at build time and not at run time, and therefore must not be "made public" process.env.IS_SERVER_INITIAL_BUILD = '1'; @@ -95,7 +191,7 @@ module.exports = withBundleAnalyzer(withSourceMaps({ }); 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"': ''}`); + 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"' : ''}`); } // Fixes npm packages that depend on `fs` module @@ -124,5 +220,35 @@ module.exports = withBundleAnalyzer(withSourceMaps({ return config; }, - poweredByHeader: 'NRN - With love', + + /** + * 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: 'Next Right Now - With love - https://github.com/UnlyEd/next-right-now', // See https://nextjs.org/docs/api-reference/next.config.js/disabling-x-powered-by })); diff --git a/now.customer1.production.json b/now.customer1.production.json index 3b342c6e5..580f47f63 100644 --- a/now.customer1.production.json +++ b/now.customer1.production.json @@ -16,11 +16,5 @@ "SENTRY_DSN": "@nrn-sentry-dsn" } }, - "routes": [ - { - "src": "/robots.txt", - "dest": "/robots/production.txt" - } - ], "public": false } diff --git a/now.customer1.staging.json b/now.customer1.staging.json index 6dde6e04b..6d8673439 100644 --- a/now.customer1.staging.json +++ b/now.customer1.staging.json @@ -16,11 +16,5 @@ "SENTRY_DSN": "@nrn-sentry-dsn" } }, - "routes": [ - { - "src": "/robots.txt", - "dest": "/robots/!production.txt" - } - ], "public": false } diff --git a/now.customer2.production.json b/now.customer2.production.json index f8c25de3f..8ea012116 100644 --- a/now.customer2.production.json +++ b/now.customer2.production.json @@ -16,11 +16,5 @@ "SENTRY_DSN": "@nrn-sentry-dsn" } }, - "routes": [ - { - "src": "/robots.txt", - "dest": "/robots/production.txt" - } - ], "public": false } diff --git a/now.customer2.staging.json b/now.customer2.staging.json index 82b2d7050..cdde44a9a 100644 --- a/now.customer2.staging.json +++ b/now.customer2.staging.json @@ -16,11 +16,5 @@ "SENTRY_DSN": "@nrn-sentry-dsn" } }, - "routes": [ - { - "src": "/robots.txt", - "dest": "/robots/!production.txt" - } - ], "public": false } diff --git a/package.json b/package.json index bedfee328..2713686a6 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "start": "next dev --port 8888", "start:tunnel": "ngrok http 8888", "build": "yarn test:once && next build", + "build:profiler": "next build --profile", "analyse:bundle": "ANALYZE_BUNDLE=true yarn start", "svg": "npx svgr -d src/svg src/svg --ext tsx --template src/utils/svg/svgTemplate.ts", "deploy": "yarn deploy:customer1", @@ -102,7 +103,7 @@ "lodash.startswith": "4.2.1", "lodash.xorby": "4.7.0", "markdown-to-jsx": "6.11.4", - "next": "9.4.4", + "next": "9.5.4-canary.4", "next-cookies": "2.0.3", "prop-types": "15.7.2", "rc-tooltip": "4.0.3", diff --git a/src/components/doc/BuiltInFeaturesSidebar.tsx b/src/components/doc/BuiltInFeaturesSidebar.tsx index 043faf5fd..8e780a6c6 100644 --- a/src/components/doc/BuiltInFeaturesSidebar.tsx +++ b/src/components/doc/BuiltInFeaturesSidebar.tsx @@ -110,8 +110,8 @@ const BuiltInFeaturesSidebar: React.FunctionComponent = (props): JSX.Elem
); diff --git a/src/components/doc/NativeFeaturesSection.tsx b/src/components/doc/NativeFeaturesSection.tsx index ed9eac9be..07bd0df7c 100644 --- a/src/components/doc/NativeFeaturesSection.tsx +++ b/src/components/doc/NativeFeaturesSection.tsx @@ -156,7 +156,6 @@ const NativeFeaturesSection: React.FunctionComponent = (props): JSX.Eleme - Be aware that this feature is still in beta (as of v9.4), and the prop name is unstable_revalidate to make this obvious.
The RFC is still being discussed, don't hesitate to participate!
API-based regeneration (e.g: regenerate pages based on a CMS update) is still being discussed in the RFC.
diff --git a/src/components/doc/NativeFeaturesSidebar.tsx b/src/components/doc/NativeFeaturesSidebar.tsx index 6a0887a09..69b60f5b8 100644 --- a/src/components/doc/NativeFeaturesSidebar.tsx +++ b/src/components/doc/NativeFeaturesSidebar.tsx @@ -73,7 +73,7 @@ const NativeFeaturesSidebar: React.FunctionComponent = (props): JSX.Eleme
); diff --git a/src/pages/[locale]/examples/native-features/example-with-ssg-and-fallback/[albumId].tsx b/src/pages/[locale]/examples/native-features/example-with-ssg-and-fallback/[albumId].tsx index 41630d267..63883ff3b 100644 --- a/src/pages/[locale]/examples/native-features/example-with-ssg-and-fallback/[albumId].tsx +++ b/src/pages/[locale]/examples/native-features/example-with-ssg-and-fallback/[albumId].tsx @@ -28,22 +28,26 @@ const logger = createLogger({ // eslint-disable-line no-unused-vars,@typescript- label: fileLabel, }); +type PageAdditionalServerSideParams = { + albumId: string; +} + /** * Only executed on the server side at build time * Necessary when a page has dynamic routes and uses "getStaticProps" */ -export const getStaticPaths: GetStaticPaths = async (): Promise => { - const commonStaticPaths: StaticPathsOutput = await getExamplesCommonStaticPaths(); +export const getStaticPaths: GetStaticPaths = async (): Promise> => { + const commonStaticPaths: StaticPathsOutput = await getExamplesCommonStaticPaths() as StaticPathsOutput; const { paths } = commonStaticPaths; const albumIdsToPreBuild = ['1']; // Only '/album-1-with-ssg-and-fallback' is generated at build time, other will be generated on-demand map(albumIdsToPreBuild, (albumId: string): void => { - map(paths, (path: StaticPath) => { + map(paths, (path: StaticPath) => { path.params.albumId = albumId; }); }); - const staticPaths: StaticPathsOutput = { + const staticPaths: StaticPathsOutput = { ...commonStaticPaths, fallback: true, }; @@ -59,7 +63,7 @@ export const getStaticPaths: GetStaticPaths = async (): * @see https://github.com/vercel/next.js/discussions/10949#discussioncomment-6884 * @see https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation */ -export const getStaticProps: GetStaticProps = async (props: StaticPropsInput): Promise => { +export const getStaticProps: GetStaticProps = async (props: StaticPropsInput): Promise => { const commonStaticProps: StaticPropsOutput = await getExamplesCommonStaticProps(props); const { params: { albumId } } = props; @@ -185,7 +189,7 @@ const ExampleWithSSGAndFallbackAlbumPage: NextPage = (props): JSX.Element ) } - { ' | ' } + {' | '} { }: DocumentRenderProps = this.props; return ( - + {
- + ); } } diff --git a/src/types/nextjs/CommonServerSideParams.ts b/src/types/nextjs/CommonServerSideParams.ts index 615e9f051..14b96854c 100644 --- a/src/types/nextjs/CommonServerSideParams.ts +++ b/src/types/nextjs/CommonServerSideParams.ts @@ -1,13 +1,12 @@ /** - * Server side params provided to any Next.js native server-side function (SSG + SSR) - * - Static params provided to getStaticProps and getStaticPaths for static pages (when using SSG) + * Server side params provided to any page (SSG or SSR) + * - Static params provided to getStaticProps and getStaticPaths for static pages (when building SSG pages) * - Dynamic params provided to getServerSideProps (when using SSR) * - * Those params come from the route (url) being used, they are affected by "experimental.redirects" and the route name (e.g: "/folder/[id].tsx" + * Those params come from the route (url) being used, they are affected by "redirects" and the route name (e.g: "/folder/[id].tsx" * - * @see next.config.js "experimental.redirects" section for url params + * @see next.config.js "redirects" section for url params */ -export type CommonServerSideParams = { - albumId?: string; // Used by album-[albumId]-with-ssg-and-fallback page +export type CommonServerSideParams = { locale?: string; // The first path of the url is the "locale" -}; +} & E; diff --git a/src/types/nextjs/StaticPath.ts b/src/types/nextjs/StaticPath.ts index 674d816b3..929176aa1 100644 --- a/src/types/nextjs/StaticPath.ts +++ b/src/types/nextjs/StaticPath.ts @@ -1,5 +1,5 @@ import { CommonServerSideParams } from './CommonServerSideParams'; -export type StaticPath = { - params: CommonServerSideParams; +export type StaticPath = { + params: CommonServerSideParams; } diff --git a/src/types/nextjs/StaticPathsOutput.ts b/src/types/nextjs/StaticPathsOutput.ts index 2d87d2255..461a6928d 100644 --- a/src/types/nextjs/StaticPathsOutput.ts +++ b/src/types/nextjs/StaticPathsOutput.ts @@ -3,9 +3,9 @@ import { CommonServerSideParams } from './CommonServerSideParams'; /** * @see https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation */ -export type StaticPathsOutput = { - fallback: boolean; // See https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required +export type StaticPathsOutput = { + fallback: boolean | 'unstable_blocking'; // See https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required paths: (string | { - params: CommonServerSideParams; + params: CommonServerSideParams; })[]; } diff --git a/src/types/nextjs/StaticPropsInput.ts b/src/types/nextjs/StaticPropsInput.ts index 75218fd30..c4d857504 100644 --- a/src/types/nextjs/StaticPropsInput.ts +++ b/src/types/nextjs/StaticPropsInput.ts @@ -4,8 +4,8 @@ import { PreviewData } from './PreviewData'; /** * Static props given as inputs for getStaticProps */ -export type StaticPropsInput = { - params?: CommonServerSideParams; +export type StaticPropsInput = { + params?: CommonServerSideParams; preview: boolean; previewData: PreviewData; } diff --git a/src/types/nextjs/StaticPropsOutput.ts b/src/types/nextjs/StaticPropsOutput.ts index e5dff54d4..ec40d2e64 100644 --- a/src/types/nextjs/StaticPropsOutput.ts +++ b/src/types/nextjs/StaticPropsOutput.ts @@ -5,5 +5,5 @@ import { SSGPageProps } from '../pageProps/SSGPageProps'; */ export type StaticPropsOutput = { props: SSGPageProps; - unstable_revalidate?: number | boolean; + revalidate?: number | boolean; } diff --git a/src/utils/nextjs/SSG.ts b/src/utils/nextjs/SSG.ts index e2e7389f8..32f20dc9a 100644 --- a/src/utils/nextjs/SSG.ts +++ b/src/utils/nextjs/SSG.ts @@ -139,7 +139,7 @@ export const getExamplesCommonStaticProps: GetStaticProps=0.10.0: +whatwg-fetch@>=0.10.0: version "3.0.0" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== @@ -12116,13 +11922,6 @@ worker-farm@^1.7.0: dependencies: errno "~0.1.7" -worker-rpc@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5" - integrity sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg== - dependencies: - microevent.ts "~0.1.1" - wrap-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba"