diff --git a/packages/now-next/src/index.ts b/packages/now-next/src/index.ts index 6b7e73e91ff..7df103f7e9d 100644 --- a/packages/now-next/src/index.ts +++ b/packages/now-next/src/index.ts @@ -1658,6 +1658,10 @@ export const build = async ({ ); } + const isNotFound = prerenderManifest.notFoundRoutes.includes( + routeFileNoExt + ); + const htmlFsRef = isBlocking ? // Blocking pages do not have an HTML fallback null @@ -1745,7 +1749,7 @@ export const build = async ({ lambda = lambdas[outputSrcPathPage]; } - if (initialRevalidate === false) { + if (!isNotFound && initialRevalidate === false) { if (htmlFsRef == null || jsonFsRef == null) { throw new NowBuildError({ code: 'NEXT_HTMLFSREF_JSONFSREF', @@ -1760,7 +1764,7 @@ export const build = async ({ } } - if (prerenders[outputPathPage] == null) { + if (prerenders[outputPathPage] == null && !isNotFound) { if (lambda == null) { throw new NowBuildError({ code: 'NEXT_MISSING_LAMBDA', diff --git a/packages/now-next/src/utils.ts b/packages/now-next/src/utils.ts index e19f86753ef..2b00e85a812 100644 --- a/packages/now-next/src/utils.ts +++ b/packages/now-next/src/utils.ts @@ -752,6 +752,8 @@ export type NextPrerenderedRoutes = { }; omittedRoutes: string[]; + + notFoundRoutes: string[]; }; export async function getExportIntent( @@ -842,6 +844,7 @@ export async function getPrerenderManifest( fallbackRoutes: {}, bypassToken: null, omittedRoutes: [], + notFoundRoutes: [], }; } @@ -887,6 +890,7 @@ export async function getPrerenderManifest( preview: { previewModeId: string; }; + notFoundRoutes?: string[]; } = JSON.parse(await fs.readFile(pathPrerenderManifest, 'utf8')); switch (manifest.version) { @@ -901,6 +905,7 @@ export async function getPrerenderManifest( bypassToken: (manifest.preview && manifest.preview.previewModeId) || null, omittedRoutes: [], + notFoundRoutes: [], }; routes.forEach(route => { @@ -955,8 +960,13 @@ export async function getPrerenderManifest( fallbackRoutes: {}, bypassToken: manifest.preview.previewModeId, omittedRoutes: [], + notFoundRoutes: [], }; + if (manifest.notFoundRoutes) { + ret.notFoundRoutes.push(...manifest.notFoundRoutes); + } + routes.forEach(route => { const { initialRevalidateSeconds, @@ -1010,6 +1020,7 @@ export async function getPrerenderManifest( fallbackRoutes: {}, bypassToken: null, omittedRoutes: [], + notFoundRoutes: [], }; } } diff --git a/packages/now-next/test/fixtures/00-i18n-support-no-shared-lambdas/next.config.js b/packages/now-next/test/fixtures/00-i18n-support-no-shared-lambdas/next.config.js index 4f05fa39ab8..249131fd3fe 100644 --- a/packages/now-next/test/fixtures/00-i18n-support-no-shared-lambdas/next.config.js +++ b/packages/now-next/test/fixtures/00-i18n-support-no-shared-lambdas/next.config.js @@ -1,4 +1,7 @@ module.exports = { + generateBuildId() { + return 'testing-build-id'; + }, experimental: { i18n: { locales: ['nl-NL', 'nl-BE', 'nl', 'fr-BE', 'fr', 'en-US', 'en'], diff --git a/packages/now-next/test/fixtures/00-i18n-support-no-shared-lambdas/pages/not-found/fallback/[slug].js b/packages/now-next/test/fixtures/00-i18n-support-no-shared-lambdas/pages/not-found/fallback/[slug].js new file mode 100644 index 00000000000..992fec8c387 --- /dev/null +++ b/packages/now-next/test/fixtures/00-i18n-support-no-shared-lambdas/pages/not-found/fallback/[slug].js @@ -0,0 +1,50 @@ +import Link from 'next/link'; +import { useRouter } from 'next/router'; + +export default function Page(props) { + const router = useRouter(); + + if (router.isFallback) return 'Loading...'; + + 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 { + unstable_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: true, + }; +}; diff --git a/packages/now-next/test/fixtures/00-i18n-support-no-shared-lambdas/pages/not-found/index.js b/packages/now-next/test/fixtures/00-i18n-support-no-shared-lambdas/pages/not-found/index.js new file mode 100644 index 00000000000..111218ca7c3 --- /dev/null +++ b/packages/now-next/test/fixtures/00-i18n-support-no-shared-lambdas/pages/not-found/index.js @@ -0,0 +1,37 @@ +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 = ({ locale, locales }) => { + if (locale === 'en' || locale === 'nl') { + return { + unstable_notFound: true, + }; + } + + return { + props: { + locale, + locales, + }, + }; +}; diff --git a/packages/now-next/test/fixtures/00-i18n-support/next.config.js b/packages/now-next/test/fixtures/00-i18n-support/next.config.js index 4f05fa39ab8..249131fd3fe 100644 --- a/packages/now-next/test/fixtures/00-i18n-support/next.config.js +++ b/packages/now-next/test/fixtures/00-i18n-support/next.config.js @@ -1,4 +1,7 @@ module.exports = { + generateBuildId() { + return 'testing-build-id'; + }, experimental: { i18n: { locales: ['nl-NL', 'nl-BE', 'nl', 'fr-BE', 'fr', 'en-US', 'en'], diff --git a/packages/now-next/test/fixtures/00-i18n-support/now.json b/packages/now-next/test/fixtures/00-i18n-support/now.json index 8ca9d6b615b..fefc2020392 100644 --- a/packages/now-next/test/fixtures/00-i18n-support/now.json +++ b/packages/now-next/test/fixtures/00-i18n-support/now.json @@ -310,6 +310,81 @@ "path": "/fr/gssp/first", "status": 200, "mustContain": "slug\":\"first\"" + }, + + // TODO: update when directory listing is disabled + // and these are proper 404s + { + "path": "/en/not-found", + "status": 200, + "mustContain": "Index of" + }, + { + "path": "/nl/not-found", + "status": 200, + "mustContain": "Index of" + }, + { + "path": "/en-US/not-found", + "status": 200, + "mustContain": "lang=\"en-US\"" + }, + { + "path": "/nl-NL/not-found", + "status": 200, + "mustContain": "lang=\"nl-NL\"" + }, + { + "path": "/fr/not-found", + "status": 200, + "mustContain": "lang=\"fr\"" + }, + + // this will always be a 200 unless fallback: blocking is used + // since the static fallback page is served before the 404 + // page is rendered + { + "path": "/en/not-found/fallback/first", + "status": 200, + "mustContain": "lang=\"en\"" + }, + { + "path": "/en/not-found/fallback/first", + "status": 200, + "mustNotContain": "gsp page" + }, + { + "path": "/_next/data/testing-build-id/en/not-found/fallback/first.json", + "status": 404 + }, + { + "path": "/en/not-found/fallback/first", + "status": 200, + "mustContain": "lang=\"en\"" + }, + { + "path": "/en/not-found/fallback/first", + "status": 200, + "mustNotContain": "gsp page" + }, + { + "path": "/fr/not-found/fallback/first", + "status": 200, + "mustContain": "lang=\"fr\"" + }, + { + "path": "/_next/data/testing-build-id/fr/not-found/fallback/first.json", + "status": 200 + }, + { + "path": "/fr/not-found/fallback/first", + "status": 200, + "mustContain": "lang=\"fr\"" + }, + { + "path": "/fr/not-found/fallback/first", + "status": 200, + "mustContain": "gsp page" } ] } diff --git a/packages/now-next/test/fixtures/00-i18n-support/pages/not-found/fallback/[slug].js b/packages/now-next/test/fixtures/00-i18n-support/pages/not-found/fallback/[slug].js new file mode 100644 index 00000000000..992fec8c387 --- /dev/null +++ b/packages/now-next/test/fixtures/00-i18n-support/pages/not-found/fallback/[slug].js @@ -0,0 +1,50 @@ +import Link from 'next/link'; +import { useRouter } from 'next/router'; + +export default function Page(props) { + const router = useRouter(); + + if (router.isFallback) return 'Loading...'; + + 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 { + unstable_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: true, + }; +}; diff --git a/packages/now-next/test/fixtures/00-i18n-support/pages/not-found/index.js b/packages/now-next/test/fixtures/00-i18n-support/pages/not-found/index.js new file mode 100644 index 00000000000..111218ca7c3 --- /dev/null +++ b/packages/now-next/test/fixtures/00-i18n-support/pages/not-found/index.js @@ -0,0 +1,37 @@ +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 = ({ locale, locales }) => { + if (locale === 'en' || locale === 'nl') { + return { + unstable_notFound: true, + }; + } + + return { + props: { + locale, + locales, + }, + }; +};