From 10e9081945a99f41da41335f928f0a3ee02ba87a Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 12 Feb 2020 22:47:26 -0500 Subject: [PATCH] Retry Static Data Fetch on Hydration --- .../next/next-server/lib/router/router.ts | 23 ++++++++++++------- .../prerender/pages/blog/[post]/index.js | 8 +++++++ test/integration/prerender/pages/index.js | 3 +++ test/integration/prerender/test/index.test.js | 15 ++++++++++++ 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/packages/next/next-server/lib/router/router.ts b/packages/next/next-server/lib/router/router.ts index e2f45975414f09b..51090ffd9b90578 100644 --- a/packages/next/next-server/lib/router/router.ts +++ b/packages/next/next-server/lib/router/router.ts @@ -70,19 +70,26 @@ function fetchNextData( isServerRender: boolean, cb?: (...args: any) => any ) { - return fetch( - formatWithValidation({ - // @ts-ignore __NEXT_DATA__ - pathname: `/_next/data/${__NEXT_DATA__.buildId}${pathname}.json`, - query, - }) - ) - .then(res => { + let attempts = isServerRender ? 3 : 1 + function getResponse(): Promise { + return fetch( + formatWithValidation({ + // @ts-ignore __NEXT_DATA__ + pathname: `/_next/data/${__NEXT_DATA__.buildId}${pathname}.json`, + query, + }) + ).then(res => { if (!res.ok) { + if (--attempts > 0 && res.status >= 500) { + return getResponse() + } throw new Error(`Failed to load static props`) } return res.json() }) + } + + return getResponse() .then(data => { return cb ? cb(data) : data }) diff --git a/test/integration/prerender/pages/blog/[post]/index.js b/test/integration/prerender/pages/blog/[post]/index.js index 6f73040dd74309e..e96ceaf1a2761a5 100644 --- a/test/integration/prerender/pages/blog/[post]/index.js +++ b/test/integration/prerender/pages/blog/[post]/index.js @@ -16,6 +16,8 @@ export async function unstable_getStaticPaths() { } } +let counter = 0 + // eslint-disable-next-line camelcase export async function unstable_getStaticProps({ params }) { if (params.post === 'post-10') { @@ -28,6 +30,12 @@ export async function unstable_getStaticProps({ params }) { throw new Error('such broken..') } + if (params.post === 'post-999') { + if (++counter < 3) { + throw new Error('try again..') + } + } + return { props: { params, diff --git a/test/integration/prerender/pages/index.js b/test/integration/prerender/pages/index.js index 241ad10e4ebdd62..3d639e231c037ee 100644 --- a/test/integration/prerender/pages/index.js +++ b/test/integration/prerender/pages/index.js @@ -32,6 +32,9 @@ const Page = ({ world, time }) => { to broken + + to broken at first +
to another dynamic diff --git a/test/integration/prerender/test/index.test.js b/test/integration/prerender/test/index.test.js index ba34b8a31d55741..04e7df065665ba2 100644 --- a/test/integration/prerender/test/index.test.js +++ b/test/integration/prerender/test/index.test.js @@ -348,6 +348,21 @@ const runTests = (dev = false) => { expect(await browser.eval('window.beforeClick')).not.toBe('true') }) + // TODO: dev currently renders this page as blocking, meaning it shows the + // server error instead of continously retrying. Do we want to change this? + if (!dev) { + it('should reload page on failed data request, and retry', async () => { + const browser = await webdriver(appPort, '/') + await browser.eval('window.beforeClick = true') + await browser.elementByCss('#broken-at-first-post').click() + await waitFor(3000) + expect(await browser.eval('window.beforeClick')).not.toBe('true') + + const text = await browser.elementByCss('#params').text() + expect(text).toMatch(/post.*?post-999/) + }) + } + it('should support prerendered catchall route', async () => { const html = await renderViaHTTP(appPort, '/catchall/another/value') const $ = cheerio.load(html)