diff --git a/packages/now-next/src/index.ts b/packages/now-next/src/index.ts index c1f3dcf9289..904b5fa6b32 100644 --- a/packages/now-next/src/index.ts +++ b/packages/now-next/src/index.ts @@ -612,13 +612,12 @@ export const build = async ({ // Prerendered routes emit a `.html` file but should not be treated as a // static page. - // Lazily prerendered routes do not have a `.html` file so we don't need - // to check/skip it here. + // Lazily prerendered routes have a fallback `.html` file on newer + // Next.js versions so we need to also not treat it as a static page here. if ( - Object.prototype.hasOwnProperty.call( - prerenderManifest.routes, - routeName - ) + prerenderManifest.routes[routeName] || + (prerenderManifest.lazyRoutes[routeName] && + prerenderManifest.lazyRoutes[routeName].fallback) ) { return; } @@ -848,12 +847,18 @@ export const build = async ({ const onPrerenderRoute = (routeKey: string, isLazy: boolean) => { // Get the route file as it'd be mounted in the builder output const routeFileNoExt = routeKey === '/' ? '/index' : routeKey; - - const htmlFsRef = isLazy - ? null - : new FileFsRef({ - fsPath: path.join(pagesDir, `${routeFileNoExt}.html`), - }); + const lazyHtmlFallback = + isLazy && prerenderManifest.lazyRoutes[routeKey].fallback; + + const htmlFsRef = + isLazy && !lazyHtmlFallback + ? null + : new FileFsRef({ + fsPath: path.join( + pagesDir, + `${lazyHtmlFallback || routeFileNoExt + '.html'}` + ), + }); const jsonFsRef = isLazy ? null : new FileFsRef({ diff --git a/packages/now-next/src/utils.ts b/packages/now-next/src/utils.ts index fd6ca5d7b8c..77c1711322a 100644 --- a/packages/now-next/src/utils.ts +++ b/packages/now-next/src/utils.ts @@ -623,6 +623,7 @@ export type NextPrerenderedRoutes = { lazyRoutes: { [route: string]: { + fallback?: string; routeRegex: string; dataRoute: string; dataRouteRegex: string; @@ -726,6 +727,7 @@ export async function getPrerenderManifest( }; dynamicRoutes: { [key: string]: { + fallback?: string; routeRegex: string; dataRoute: string; dataRouteRegex: string; @@ -759,11 +761,17 @@ export async function getPrerenderManifest( lazyRoutes.forEach(lazyRoute => { const { routeRegex, + fallback, dataRoute, dataRouteRegex, } = manifest.dynamicRoutes[lazyRoute]; - ret.lazyRoutes[lazyRoute] = { routeRegex, dataRoute, dataRouteRegex }; + ret.lazyRoutes[lazyRoute] = { + routeRegex, + fallback, + dataRoute, + dataRouteRegex, + }; }); return ret; diff --git a/packages/now-next/test/fixtures/18-ssg-fallback-support/next.config.js b/packages/now-next/test/fixtures/18-ssg-fallback-support/next.config.js new file mode 100644 index 00000000000..ac717504266 --- /dev/null +++ b/packages/now-next/test/fixtures/18-ssg-fallback-support/next.config.js @@ -0,0 +1,5 @@ +module.exports = { + generateBuildId() { + return 'testing-build-id'; + }, +}; diff --git a/packages/now-next/test/fixtures/18-ssg-fallback-support/now.json b/packages/now-next/test/fixtures/18-ssg-fallback-support/now.json new file mode 100644 index 00000000000..9cd62e8cd93 --- /dev/null +++ b/packages/now-next/test/fixtures/18-ssg-fallback-support/now.json @@ -0,0 +1,170 @@ +{ + "version": 2, + "builds": [{ "src": "package.json", "use": "@now/next" }], + "probes": [ + { + "path": "/lambda", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { + "path": "/forever", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { "delay": 2000 }, + { + "path": "/forever", + "status": 200, + "responseHeaders": { + "x-now-cache": "HIT" + } + }, + { + "path": "/another", + "status": 200, + "responseHeaders": { + "x-now-cache": "PRERENDER" + } + }, + { "delay": 2000 }, + { + "path": "/another", + "status": 200, + "responseHeaders": { + "x-now-cache": "HIT" + } + }, + { + "path": "/blog/post-1", + "status": 200, + "responseHeaders": { + "x-now-cache": "PRERENDER" + } + }, + { "delay": 2000 }, + { + "path": "/blog/post-1", + "status": 200, + "responseHeaders": { + "x-now-cache": "HIT" + } + }, + { + "path": "/blog/post-2", + "status": 200, + "responseHeaders": { + "x-now-cache": "PRERENDER" + } + }, + { "delay": 2000 }, + { + "path": "/blog/post-2", + "status": 200, + "responseHeaders": { + "x-now-cache": "HIT" + } + }, + { + "path": "/blog/post-3", + "status": 200, + "mustContain": "loading..." + }, + { "delay": 2000 }, + { + "path": "/blog/post-3", + "status": 200, + "responseHeaders": { + "x-now-cache": "/HIT|STALE/" + } + }, + { + "path": "/blog/post-3", + "status": 200, + "mustContain": "post-3" + }, + { + "path": "/blog/post-1/comment-1", + "status": 200, + "responseHeaders": { + "x-now-cache": "PRERENDER" + } + }, + { + "path": "/blog/post-2/comment-2", + "status": 200, + "responseHeaders": { + "x-now-cache": "PRERENDER" + } + }, + { + "path": "/blog/post-3/comment-3", + "status": 200, + "mustContain": "loading..." + }, + { + "path": "/_next/data/testing-build-id/lambda.json", + "status": 404 + }, + { + "path": "/_next/data/testing-build-id/forever.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { "delay": 2000 }, + { + "path": "/blog/post-3/comment-3", + "status": 200, + "mustContain": "comment-3" + }, + { + "path": "/_next/data/testing-build-id/forever.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "HIT" + } + }, + { + "path": "/_next/data/testing-build-id/another.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "/HIT|STALE/" + } + }, + { + "path": "/_next/data/testing-build-id/another2.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "PRERENDER" + } + }, + { "delay": 2000 }, + { + "path": "/_next/data/testing-build-id/another2.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "HIT" + } + }, + { + "path": "/_next/data/testing-build-id/blog/post-1.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "/HIT|STALE|PRERENDER/" + } + }, + { + "path": "/_next/data/testing-build-id/blog/post-1337/comment-1337.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "PRERENDER" + } + } + ] +} diff --git a/packages/now-next/test/fixtures/18-ssg-fallback-support/package.json b/packages/now-next/test/fixtures/18-ssg-fallback-support/package.json new file mode 100644 index 00000000000..93e31bc44d9 --- /dev/null +++ b/packages/now-next/test/fixtures/18-ssg-fallback-support/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "next": "9.2.2-canary.16", + "react": "^16.8.6", + "react-dom": "^16.8.6" + } +} diff --git a/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/another.js b/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/another.js new file mode 100644 index 00000000000..077b6b248ef --- /dev/null +++ b/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/another.js @@ -0,0 +1,21 @@ +import React from 'react'; + +// eslint-disable-next-line camelcase +export async function unstable_getStaticProps() { + return { + props: { + world: 'world', + time: new Date().getTime(), + }, + revalidate: 5, + }; +} + +export default ({ world, time }) => { + return ( + <> +

hello: {world}

+ time: {time} + + ); +}; diff --git a/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/another2.js b/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/another2.js new file mode 100644 index 00000000000..077b6b248ef --- /dev/null +++ b/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/another2.js @@ -0,0 +1,21 @@ +import React from 'react'; + +// eslint-disable-next-line camelcase +export async function unstable_getStaticProps() { + return { + props: { + world: 'world', + time: new Date().getTime(), + }, + revalidate: 5, + }; +} + +export default ({ world, time }) => { + return ( + <> +

hello: {world}

+ time: {time} + + ); +}; diff --git a/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/blog/[post]/[comment].js b/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/blog/[post]/[comment].js new file mode 100644 index 00000000000..6f2f7115e4f --- /dev/null +++ b/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/blog/[post]/[comment].js @@ -0,0 +1,36 @@ +import React from 'react'; + +// eslint-disable-next-line camelcase +export async function unstable_getStaticPaths () { + return { + paths: [ + '/blog/post-1/comment-1', + { params: { post: 'post-2', comment: 'comment-2' } }, + '/blog/post-1337/comment-1337', + ] + } +} + +// eslint-disable-next-line camelcase +export async function unstable_getStaticProps ({ params }) { + return { + props: { + post: params.post, + comment: params.comment, + time: new Date().getTime(), + }, + revalidate: 2, + }; +} + +export default ({ post, comment, time }) => { + if (!post) return

loading...

+ + return ( + <> +

Post: {post}

+

Comment: {comment}

+ time: {time} + + ); +}; diff --git a/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/blog/[post]/index.js b/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/blog/[post]/index.js new file mode 100644 index 00000000000..ba348e6f3e1 --- /dev/null +++ b/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/blog/[post]/index.js @@ -0,0 +1,40 @@ +import React from 'react' + +// eslint-disable-next-line camelcase +export async function unstable_getStaticPaths () { + return { + paths: [ + '/blog/post-1', + { params: { post: 'post-2' } }, + ] + } +} + + +// eslint-disable-next-line camelcase +export async function unstable_getStaticProps ({ params }) { + if (params.post === 'post-10') { + await new Promise(resolve => { + setTimeout(() => resolve(), 1000) + }) + } + + return { + props: { + post: params.post, + time: (await import('perf_hooks')).performance.now() + }, + revalidate: 10 + } +} + +export default ({ post, time }) => { + if (!post) return

loading...

+ + return ( + <> +

Post: {post}

+ time: {time} + + ) +} diff --git a/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/forever.js b/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/forever.js new file mode 100644 index 00000000000..ffd0ddfb736 --- /dev/null +++ b/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/forever.js @@ -0,0 +1,21 @@ +import React from 'react'; + +// eslint-disable-next-line camelcase +export async function unstable_getStaticProps() { + return { + props: { + world: 'world', + time: new Date().getTime(), + }, + revalidate: false, + }; +} + +export default ({ world, time }) => { + return ( + <> +

hello: {world}

+ time: {time} + + ); +}; diff --git a/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/index.js b/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/index.js new file mode 100644 index 00000000000..cdeee3e6335 --- /dev/null +++ b/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/index.js @@ -0,0 +1 @@ +export default () => 'Hi'; diff --git a/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/lambda.js b/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/lambda.js new file mode 100644 index 00000000000..c2a3ce85b6a --- /dev/null +++ b/packages/now-next/test/fixtures/18-ssg-fallback-support/pages/lambda.js @@ -0,0 +1,5 @@ +const Page = ({ data }) =>

{data} world

; + +Page.getInitialProps = () => ({ data: 'hello' }); + +export default Page; diff --git a/packages/now-next/test/unit/build.test.js b/packages/now-next/test/unit/build.test.js index ded71c5965f..f6fb3614a67 100644 --- a/packages/now-next/test/unit/build.test.js +++ b/packages/now-next/test/unit/build.test.js @@ -115,7 +115,7 @@ describe('build meta dev', () => { { src: '/nested/page', dest: 'http://localhost:5000/nested/page' }, { src: '/api/test', dest: 'http://localhost:5000/api/test' }, { - src: '^/(nested\\/([^\\/]+?)(?:\\/)?)$', + src: '^/(nested\\/([^/]+?)(?:\\/)?)$', dest: 'http://localhost:5000/$1', }, { src: '/data.txt', dest: 'http://localhost:5000/data.txt' },