From 0f2505147031ffa4e767a4cce2a8634a32df42d8 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 27 Oct 2020 10:11:51 -0500 Subject: [PATCH] Ensure getStaticProps is called for SSG 404 in blocking mode (#18300) This ensures that when using a `pages/404` file with `getStaticProps` that we call `getStaticProps` in `fallback: 'blocking'` mode Fixes: https://github.com/vercel/next.js/issues/18293 --- .../webpack/loaders/next-serverless-loader.ts | 10 ++- .../not-found/blocking-fallback/[slug].js | 48 +++++++++++ .../i18n-support/test/index.test.js | 86 +++++++++++++++++++ 3 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 test/integration/i18n-support/pages/not-found/blocking-fallback/[slug].js diff --git a/packages/next/build/webpack/loaders/next-serverless-loader.ts b/packages/next/build/webpack/loaders/next-serverless-loader.ts index 013f63562d0a804..5b24329dd36b3de 100644 --- a/packages/next/build/webpack/loaders/next-serverless-loader.ts +++ b/packages/next/build/webpack/loaders/next-serverless-loader.ts @@ -492,7 +492,7 @@ const nextServerlessLoader: loader.Loader = function () { export async function renderReqToHTML(req, res, renderMode, _renderOpts, _params) { let Document let Error - let NotFound + let notFoundMod ;[ getStaticProps, getServerSideProps, @@ -502,7 +502,7 @@ const nextServerlessLoader: loader.Loader = function () { config, { default: Document }, { default: Error }, - ${absolute404Path ? `{ default: NotFound }, ` : ''} + ${absolute404Path ? `notFoundMod, ` : ''} ] = await Promise.all([ getStaticProps, getServerSideProps, @@ -772,13 +772,15 @@ const nextServerlessLoader: loader.Loader = function () { res.statusCode = 404 const NotFoundComponent = ${ - absolute404Path ? 'NotFound' : 'Error' + absolute404Path ? 'notFoundMod.default' : 'Error' } const errPathname = "${absolute404Path ? '/404' : '/_error'}" const result = await renderToHTML(req, res, errPathname, parsedUrl.query, Object.assign({}, options, { - getStaticProps: undefined, + getStaticProps: ${ + absolute404Path ? `notFoundMod.getStaticProps` : 'undefined' + }, getStaticPaths: undefined, getServerSideProps: undefined, Component: NotFoundComponent, diff --git a/test/integration/i18n-support/pages/not-found/blocking-fallback/[slug].js b/test/integration/i18n-support/pages/not-found/blocking-fallback/[slug].js new file mode 100644 index 000000000000000..7257f7422968d9c --- /dev/null +++ b/test/integration/i18n-support/pages/not-found/blocking-fallback/[slug].js @@ -0,0 +1,48 @@ +import Link from 'next/link' +import { useRouter } from 'next/router' + +export default function Page(props) { + const router = useRouter() + + return ( + <> +

gsp page

+

{JSON.stringify(props)}

+

{router.locale}

+

{JSON.stringify(router.locales)}

+

{JSON.stringify(router.query)}

+

{router.pathname}

+

{router.asPath}

+ + to / + +
+ + ) +} + +export const getStaticProps = ({ params, locale, locales }) => { + if (locale === 'en' || locale === 'nl') { + return { + notFound: true, + } + } + + return { + props: { + params, + locale, + locales, + }, + } +} + +export const getStaticPaths = () => { + return { + // the default locale will be used since one isn't defined here + paths: ['first', 'second'].map((slug) => ({ + params: { slug }, + })), + fallback: 'blocking', + } +} diff --git a/test/integration/i18n-support/test/index.test.js b/test/integration/i18n-support/test/index.test.js index 583d898bd669fa4..ae2801777667698 100644 --- a/test/integration/i18n-support/test/index.test.js +++ b/test/integration/i18n-support/test/index.test.js @@ -106,6 +106,16 @@ function runTests(isDev) { initialRevalidateSeconds: false, srcRoute: '/gsp/no-fallback/[slug]', }, + '/en-US/not-found/blocking-fallback/first': { + dataRoute: `/_next/data/${buildId}/en-US/not-found/blocking-fallback/first.json`, + initialRevalidateSeconds: false, + srcRoute: '/not-found/blocking-fallback/[slug]', + }, + '/en-US/not-found/blocking-fallback/second': { + dataRoute: `/_next/data/${buildId}/en-US/not-found/blocking-fallback/second.json`, + initialRevalidateSeconds: false, + srcRoute: '/not-found/blocking-fallback/[slug]', + }, '/en-US/not-found/fallback/first': { dataRoute: `/_next/data/${buildId}/en-US/not-found/fallback/first.json`, initialRevalidateSeconds: false, @@ -157,6 +167,18 @@ function runTests(isDev) { )}/gsp/no\\-fallback/([^/]+?)\\.json$` ), }, + '/not-found/blocking-fallback/[slug]': { + dataRoute: `/_next/data/${buildId}/not-found/blocking-fallback/[slug].json`, + dataRouteRegex: normalizeRegEx( + `^\\/_next\\/data\\/${escapeRegex( + buildId + )}\\/not\\-found\\/blocking\\-fallback\\/([^\\/]+?)\\.json$` + ), + fallback: null, + routeRegex: normalizeRegEx( + `^\\/not\\-found\\/blocking\\-fallback\\/([^\\/]+?)(?:\\/)?$` + ), + }, '/not-found/fallback/[slug]': { dataRoute: `/_next/data/${buildId}/not-found/fallback/[slug].json`, dataRouteRegex: normalizeRegEx( @@ -957,6 +979,70 @@ function runTests(isDev) { expect(await browser.eval('window.beforeNav')).toBe(1) }) + it('should render 404 for blocking fallback page that returned 404 on client transition', async () => { + const browser = await webdriver(appPort, '/en', true, true) + await browser.eval(`(function() { + next.router.push('/not-found/blocking-fallback/first') + })()`) + await browser.waitForElementByCss('h1') + await browser.eval('window.beforeNav = 1') + + expect(await browser.elementByCss('html').text()).toContain( + 'This page could not be found' + ) + const props = JSON.parse(await browser.elementByCss('#props').text()) + + expect(props.is404).toBe(true) + expect(props.locale).toBe('en') + expect(await browser.elementByCss('html').getAttribute('lang')).toBe('en') + + const parsedUrl = url.parse( + await browser.eval('window.location.href'), + true + ) + expect(parsedUrl.pathname).toBe('/en/not-found/blocking-fallback/first') + expect(parsedUrl.query).toEqual({}) + + if (isDev) { + // make sure page doesn't reload un-necessarily in development + await waitFor(10 * 1000) + } + expect(await browser.eval('window.beforeNav')).toBe(1) + }) + + it('should render 404 for blocking fallback page that returned 404', async () => { + const browser = await webdriver( + appPort, + '/en/not-found/blocking-fallback/first', + true, + true + ) + await browser.waitForElementByCss('h1') + await browser.eval('window.beforeNav = 1') + + expect(await browser.elementByCss('html').text()).toContain( + 'This page could not be found' + ) + const props = JSON.parse(await browser.elementByCss('#props').text()) + + expect(props.is404).toBe(true) + expect(props.locale).toBe('en') + expect(await browser.elementByCss('html').getAttribute('lang')).toBe('en') + + const parsedUrl = url.parse( + await browser.eval('window.location.href'), + true + ) + expect(parsedUrl.pathname).toBe('/en/not-found/blocking-fallback/first') + expect(parsedUrl.query).toEqual({}) + + if (isDev) { + // make sure page doesn't reload un-necessarily in development + await waitFor(10 * 1000) + } + expect(await browser.eval('window.beforeNav')).toBe(1) + }) + it('should not remove locale prefix for default locale', async () => { const res = await fetchViaHTTP(appPort, '/en-US', undefined, { redirect: 'manual',