From 74983c07750c3eb729581086437f76e2a001f95e Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 18 Jun 2024 16:33:29 +0200 Subject: [PATCH] test: refactor pages router on-demand revalidation test and add test cases for not prerendered content --- tests/e2e/page-router.test.ts | 1532 +++++++++-------- .../pages/products/[slug].js | 40 + .../page-router/pages/api/revalidate.js | 3 +- .../page-router/pages/products/[slug].js | 13 +- tests/integration/static.test.ts | 1 + 5 files changed, 830 insertions(+), 759 deletions(-) create mode 100644 tests/fixtures/page-router-base-path-i18n/pages/products/[slug].js diff --git a/tests/e2e/page-router.test.ts b/tests/e2e/page-router.test.ts index 9fe7416412..09f9e3f2ad 100644 --- a/tests/e2e/page-router.test.ts +++ b/tests/e2e/page-router.test.ts @@ -49,189 +49,204 @@ export async function check( } test.describe('Simple Page Router (no basePath, no i18n)', () => { - test('On-demand revalidate works correctly', async ({ - page, - pollUntilHeadersMatch, - pageRouter, - }) => { - // in case there is retry or some other test did hit that path before - // we want to make sure that cdn cache is not warmed up - const purgeCdnCache = await page.goto( - new URL('/api/purge-cdn?path=/static/revalidate-manual', pageRouter.url).href, - ) - expect(purgeCdnCache?.status()).toBe(200) - - // wait a bit until cdn cache purge propagates - await page.waitForTimeout(500) - - const response1 = await pollUntilHeadersMatch( - new URL('static/revalidate-manual', pageRouter.url).href, + test.describe('On-demand revalidate works correctly', () => { + for (const { label, prerendered, pagePath, expectedH1Content } of [ { - headersToMatch: { - // either first time hitting this route or we invalidated - // just CDN node in earlier step - // we will invoke function and see Next cache hit status \ - // in the response because it was prerendered at build time - // or regenerated in previous attempt to run this test - 'cache-status': [/"Netlify Edge"; fwd=(miss|stale)/m, /"Next.js"; hit/m], - }, - headersNotMatchedMessage: - 'First request to tested page (html) should be a miss or stale on the Edge and hit in Next.js', + label: 'prerendered page with static path', + prerendered: true, + pagePath: '/static/revalidate-manual', + expectedH1Content: 'Show #71', }, - ) - const headers1 = response1?.headers() || {} - expect(response1?.status()).toBe(200) - expect(headers1['x-nextjs-cache']).toBeUndefined() - expect(headers1['netlify-cache-tag']).toBe('_n_t_/static/revalidate-manual') - expect(headers1['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - const date1 = await page.textContent('[data-testid="date-now"]') - const h1 = await page.textContent('h1') - expect(h1).toBe('Show #71') - - // check json route - const response1Json = await pollUntilHeadersMatch( - new URL('_next/data/build-id/static/revalidate-manual.json', pageRouter.url).href, { - headersToMatch: { - // either first time hitting this route or we invalidated - // just CDN node in earlier step - // we will invoke function and see Next cache hit status \ - // in the response because it was prerendered at build time - // or regenerated in previous attempt to run this test - 'cache-status': [/"Netlify Edge"; fwd=(miss|stale)/m, /"Next.js"; hit/m], - }, - headersNotMatchedMessage: - 'First request to tested page (data) should be a miss or stale on the Edge and hit in Next.js', + label: 'prerendered page with dynamic path', + prerendered: true, + pagePath: '/products/prerendered', + expectedH1Content: 'Product prerendered', }, - ) - const headers1Json = response1Json?.headers() || {} - expect(response1Json?.status()).toBe(200) - expect(headers1Json['x-nextjs-cache']).toBeUndefined() - expect(headers1Json['netlify-cache-tag']).toBe('_n_t_/static/revalidate-manual') - expect(headers1Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - const data1 = (await response1Json?.json()) || {} - expect(data1?.pageProps?.time).toBe(date1) - - const response2 = await pollUntilHeadersMatch( - new URL('static/revalidate-manual', pageRouter.url).href, { - headersToMatch: { - // we are hitting the same page again and we most likely will see - // CDN hit (in this case Next reported cache status is omitted - // as it didn't actually take place in handling this request) - // or we will see CDN miss because different CDN node handled request - 'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m, - }, - headersNotMatchedMessage: - 'Second request to tested page (html) should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', + label: 'not prerendered page with dynamic path', + prerendered: false, + pagePath: '/products/not-prerendered', + expectedH1Content: 'Product not-prerendered', }, - ) - const headers2 = response2?.headers() || {} - expect(response2?.status()).toBe(200) - expect(headers2['x-nextjs-cache']).toBeUndefined() - if (!headers2['cache-status'].includes('"Netlify Edge"; hit')) { - // if we missed CDN cache, we will see Next cache hit status - // as we reuse cached response - expect(headers2['cache-status']).toMatch(/"Next.js"; hit/m) - } - expect(headers2['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - // the page is cached - const date2 = await page.textContent('[data-testid="date-now"]') - expect(date2).toBe(date1) - - // check json route - const response2Json = await pollUntilHeadersMatch( - new URL('/_next/data/build-id/static/revalidate-manual.json', pageRouter.url).href, - { - headersToMatch: { - // we are hitting the same page again and we most likely will see - // CDN hit (in this case Next reported cache status is omitted - // as it didn't actually take place in handling this request) - // or we will see CDN miss because different CDN node handled request - 'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m, - }, - headersNotMatchedMessage: - 'Second request to tested page (data) should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', - }, - ) - const headers2Json = response2Json?.headers() || {} - expect(response2Json?.status()).toBe(200) - expect(headers2Json['x-nextjs-cache']).toBeUndefined() - if (!headers2Json['cache-status'].includes('"Netlify Edge"; hit')) { - // if we missed CDN cache, we will see Next cache hit status - // as we reuse cached response - expect(headers2Json['cache-status']).toMatch(/"Next.js"; hit/m) - } - expect(headers2Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - const data2 = (await response2Json?.json()) || {} - expect(data2?.pageProps?.time).toBe(date1) - - const revalidate = await page.goto(new URL('/api/revalidate', pageRouter.url).href) - expect(revalidate?.status()).toBe(200) - - // wait a bit until the page got regenerated - await page.waitForTimeout(1000) + ]) { + test(label, async ({ page, pollUntilHeadersMatch, pageRouter }) => { + // in case there is retry or some other test did hit that path before + // we want to make sure that cdn cache is not warmed up + const purgeCdnCache = await page.goto( + new URL(`/api/purge-cdn?path=${pagePath}`, pageRouter.url).href, + ) + expect(purgeCdnCache?.status()).toBe(200) + + // wait a bit until cdn cache purge propagates + await page.waitForTimeout(500) + + const response1 = await pollUntilHeadersMatch(new URL(pagePath, pageRouter.url).href, { + headersToMatch: { + // either first time hitting this route or we invalidated + // just CDN node in earlier step + // we will invoke function and see Next cache hit status + // in the response because it was prerendered at build time + // or regenerated in previous attempt to run this test + 'cache-status': [ + /"Netlify Edge"; fwd=(miss|stale)/m, + prerendered ? /"Next.js"; hit/m : /"Next.js"; (hit|fwd=miss)/m, + ], + }, + headersNotMatchedMessage: + 'First request to tested page (html) should be a miss or stale on the Edge and hit in Next.js', + }) + const headers1 = response1?.headers() || {} + expect(response1?.status()).toBe(200) + expect(headers1['x-nextjs-cache']).toBeUndefined() + expect(headers1['netlify-cache-tag']).toBe(`_n_t_${pagePath}`) + expect(headers1['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + const date1 = await page.textContent('[data-testid="date-now"]') + const h1 = await page.textContent('h1') + expect(h1).toBe(expectedH1Content) + + // check json route + const response1Json = await pollUntilHeadersMatch( + new URL(`_next/data/build-id${pagePath}.json`, pageRouter.url).href, + { + headersToMatch: { + // either first time hitting this route or we invalidated + // just CDN node in earlier step + // we will invoke function and see Next cache hit status \ + // in the response because it was prerendered at build time + // or regenerated in previous attempt to run this test + 'cache-status': [/"Netlify Edge"; fwd=(miss|stale)/m, /"Next.js"; hit/m], + }, + headersNotMatchedMessage: + 'First request to tested page (data) should be a miss or stale on the Edge and hit in Next.js', + }, + ) + const headers1Json = response1Json?.headers() || {} + expect(response1Json?.status()).toBe(200) + expect(headers1Json['x-nextjs-cache']).toBeUndefined() + expect(headers1Json['netlify-cache-tag']).toBe(`_n_t_${pagePath}`) + expect(headers1Json['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + const data1 = (await response1Json?.json()) || {} + expect(data1?.pageProps?.time).toBe(date1) + + const response2 = await pollUntilHeadersMatch(new URL(pagePath, pageRouter.url).href, { + headersToMatch: { + // we are hitting the same page again and we most likely will see + // CDN hit (in this case Next reported cache status is omitted + // as it didn't actually take place in handling this request) + // or we will see CDN miss because different CDN node handled request + 'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m, + }, + headersNotMatchedMessage: + 'Second request to tested page (html) should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', + }) + const headers2 = response2?.headers() || {} + expect(response2?.status()).toBe(200) + expect(headers2['x-nextjs-cache']).toBeUndefined() + if (!headers2['cache-status'].includes('"Netlify Edge"; hit')) { + // if we missed CDN cache, we will see Next cache hit status + // as we reuse cached response + expect(headers2['cache-status']).toMatch(/"Next.js"; hit/m) + } + expect(headers2['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + // the page is cached + const date2 = await page.textContent('[data-testid="date-now"]') + expect(date2).toBe(date1) + + // check json route + const response2Json = await pollUntilHeadersMatch( + new URL(`/_next/data/build-id${pagePath}.json`, pageRouter.url).href, + { + headersToMatch: { + // we are hitting the same page again and we most likely will see + // CDN hit (in this case Next reported cache status is omitted + // as it didn't actually take place in handling this request) + // or we will see CDN miss because different CDN node handled request + 'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m, + }, + headersNotMatchedMessage: + 'Second request to tested page (data) should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', + }, + ) + const headers2Json = response2Json?.headers() || {} + expect(response2Json?.status()).toBe(200) + expect(headers2Json['x-nextjs-cache']).toBeUndefined() + if (!headers2Json['cache-status'].includes('"Netlify Edge"; hit')) { + // if we missed CDN cache, we will see Next cache hit status + // as we reuse cached response + expect(headers2Json['cache-status']).toMatch(/"Next.js"; hit/m) + } + expect(headers2Json['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) - // now after the revalidation it should have a different date - const response3 = await pollUntilHeadersMatch( - new URL('static/revalidate-manual', pageRouter.url).href, - { - headersToMatch: { - // revalidate refreshes Next cache, but not CDN cache - // so our request after revalidation means that Next cache is already - // warmed up with fresh response, but CDN cache just knows that previously - // cached response is stale, so we are hitting our function that serve - // already cached response - 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], - }, - headersNotMatchedMessage: - 'Third request to tested page (html) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', - }, - ) - const headers3 = response3?.headers() || {} - expect(response3?.status()).toBe(200) - expect(headers3?.['x-nextjs-cache']).toBeUndefined() + const data2 = (await response2Json?.json()) || {} + expect(data2?.pageProps?.time).toBe(date1) - // the page has now an updated date - const date3 = await page.textContent('[data-testid="date-now"]') - expect(date3).not.toBe(date2) + const revalidate = await page.goto( + new URL(`/api/revalidate?path=${pagePath}`, pageRouter.url).href, + ) + expect(revalidate?.status()).toBe(200) - // check json route - const response3Json = await pollUntilHeadersMatch( - new URL('/_next/data/build-id/static/revalidate-manual.json', pageRouter.url).href, - { - headersToMatch: { - // revalidate refreshes Next cache, but not CDN cache - // so our request after revalidation means that Next cache is already - // warmed up with fresh response, but CDN cache just knows that previously - // cached response is stale, so we are hitting our function that serve - // already cached response - 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], - }, - headersNotMatchedMessage: - 'Third request to tested page (data) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', - }, - ) - const headers3Json = response3Json?.headers() || {} - expect(response3Json?.status()).toBe(200) - expect(headers3Json['x-nextjs-cache']).toBeUndefined() - expect(headers3Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) + // wait a bit until the page got regenerated + await page.waitForTimeout(1000) - const data3 = (await response3Json?.json()) || {} - expect(data3?.pageProps?.time).toBe(date3) + // now after the revalidation it should have a different date + const response3 = await pollUntilHeadersMatch(new URL(pagePath, pageRouter.url).href, { + headersToMatch: { + // revalidate refreshes Next cache, but not CDN cache + // so our request after revalidation means that Next cache is already + // warmed up with fresh response, but CDN cache just knows that previously + // cached response is stale, so we are hitting our function that serve + // already cached response + 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], + }, + headersNotMatchedMessage: + 'Third request to tested page (html) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', + }) + const headers3 = response3?.headers() || {} + expect(response3?.status()).toBe(200) + expect(headers3?.['x-nextjs-cache']).toBeUndefined() + + // the page has now an updated date + const date3 = await page.textContent('[data-testid="date-now"]') + expect(date3).not.toBe(date2) + + // check json route + const response3Json = await pollUntilHeadersMatch( + new URL(`/_next/data/build-id${pagePath}.json`, pageRouter.url).href, + { + headersToMatch: { + // revalidate refreshes Next cache, but not CDN cache + // so our request after revalidation means that Next cache is already + // warmed up with fresh response, but CDN cache just knows that previously + // cached response is stale, so we are hitting our function that serve + // already cached response + 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], + }, + headersNotMatchedMessage: + 'Third request to tested page (data) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', + }, + ) + const headers3Json = response3Json?.headers() || {} + expect(response3Json?.status()).toBe(200) + expect(headers3Json['x-nextjs-cache']).toBeUndefined() + expect(headers3Json['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + const data3 = (await response3Json?.json()) || {} + expect(data3?.pageProps?.time).toBe(date3) + }) + } }) test('Time based revalidate works correctly', async ({ @@ -396,588 +411,595 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { test.describe('Page Router with basePath and i18n', () => { test.describe('Static revalidate works correctly', () => { - test('default locale', async ({ page, pollUntilHeadersMatch, pageRouterBasePathI18n }) => { - // in case there is retry or some other test did hit that path before - // we want to make sure that cdn cache is not warmed up - const purgeCdnCache = await page.goto( - new URL( - '/base/path/api/purge-cdn?path=/en/static/revalidate-manual', - pageRouterBasePathI18n.url, - ).href, - ) - expect(purgeCdnCache?.status()).toBe(200) - - // wait a bit until cdn cache purge propagates - await page.waitForTimeout(500) - - const response1ImplicitLocale = await pollUntilHeadersMatch( - new URL('base/path/static/revalidate-manual', pageRouterBasePathI18n.url).href, - { - headersToMatch: { - // either first time hitting this route or we invalidated - // just CDN node in earlier step - // we will invoke function and see Next cache hit status \ - // in the response because it was prerendered at build time - // or regenerated in previous attempt to run this test - 'cache-status': [/"Netlify Edge"; fwd=(miss|stale)/m, /"Next.js"; hit/m], - }, - headersNotMatchedMessage: - 'First request to tested page (implicit locale html) should be a miss or stale on the Edge and hit in Next.js', - }, - ) - const headers1ImplicitLocale = response1ImplicitLocale?.headers() || {} - expect(response1ImplicitLocale?.status()).toBe(200) - expect(headers1ImplicitLocale['x-nextjs-cache']).toBeUndefined() - expect(headers1ImplicitLocale['netlify-cache-tag']).toBe('_n_t_/en/static/revalidate-manual') - expect(headers1ImplicitLocale['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - const date1ImplicitLocale = await page.textContent('[data-testid="date-now"]') - const h1ImplicitLocale = await page.textContent('h1') - expect(h1ImplicitLocale).toBe('Show #71') - - const response1ExplicitLocale = await pollUntilHeadersMatch( - new URL('base/path/en/static/revalidate-manual', pageRouterBasePathI18n.url).href, - { - headersToMatch: { - // either first time hitting this route or we invalidated - // just CDN node in earlier step - // we will invoke function and see Next cache hit status \ - // in the response because it was prerendered at build time - // or regenerated in previous attempt to run this test - 'cache-status': [/"Netlify Edge"; fwd=(miss|stale)/m, /"Next.js"; hit/m], - }, - headersNotMatchedMessage: - 'First request to tested page (explicit locale html) should be a miss or stale on the Edge and hit in Next.js', - }, - ) - const headers1ExplicitLocale = response1ExplicitLocale?.headers() || {} - expect(response1ExplicitLocale?.status()).toBe(200) - expect(headers1ExplicitLocale['x-nextjs-cache']).toBeUndefined() - expect(headers1ExplicitLocale['netlify-cache-tag']).toBe('_n_t_/en/static/revalidate-manual') - expect(headers1ExplicitLocale['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - const date1ExplicitLocale = await page.textContent('[data-testid="date-now"]') - const h1ExplicitLocale = await page.textContent('h1') - expect(h1ExplicitLocale).toBe('Show #71') - - // implicit and explicit locale paths should be the same (same cached response) - expect(date1ImplicitLocale).toBe(date1ExplicitLocale) - - // check json route - const response1Json = await pollUntilHeadersMatch( - new URL( - 'base/path/_next/data/build-id/en/static/revalidate-manual.json', - pageRouterBasePathI18n.url, - ).href, - { - headersToMatch: { - // either first time hitting this route or we invalidated - // just CDN node in earlier step - // we will invoke function and see Next cache hit status \ - // in the response because it was prerendered at build time - // or regenerated in previous attempt to run this test - 'cache-status': [/"Netlify Edge"; fwd=(miss|stale)/m, /"Next.js"; hit/m], - }, - headersNotMatchedMessage: - 'First request to tested page (data) should be a miss or stale on the Edge and hit in Next.js', - }, - ) - const headers1Json = response1Json?.headers() || {} - expect(response1Json?.status()).toBe(200) - expect(headers1Json['x-nextjs-cache']).toBeUndefined() - expect(headers1Json['netlify-cache-tag']).toBe('_n_t_/en/static/revalidate-manual') - expect(headers1Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - const data1 = (await response1Json?.json()) || {} - expect(data1?.pageProps?.time).toBe(date1ImplicitLocale) - - const response2ImplicitLocale = await pollUntilHeadersMatch( - new URL('base/path/static/revalidate-manual', pageRouterBasePathI18n.url).href, - { - headersToMatch: { - // we are hitting the same page again and we most likely will see - // CDN hit (in this case Next reported cache status is omitted - // as it didn't actually take place in handling this request) - // or we will see CDN miss because different CDN node handled request - 'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m, - }, - headersNotMatchedMessage: - 'Second request to tested page (implicit locale html) should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', - }, - ) - const headers2ImplicitLocale = response2ImplicitLocale?.headers() || {} - expect(response2ImplicitLocale?.status()).toBe(200) - expect(headers2ImplicitLocale['x-nextjs-cache']).toBeUndefined() - if (!headers2ImplicitLocale['cache-status'].includes('"Netlify Edge"; hit')) { - // if we missed CDN cache, we will see Next cache hit status - // as we reuse cached response - expect(headers2ImplicitLocale['cache-status']).toMatch(/"Next.js"; hit/m) - } - expect(headers2ImplicitLocale['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - // the page is cached - const date2ImplicitLocale = await page.textContent('[data-testid="date-now"]') - expect(date2ImplicitLocale).toBe(date1ImplicitLocale) - - const response2ExplicitLocale = await pollUntilHeadersMatch( - new URL('base/path/static/revalidate-manual', pageRouterBasePathI18n.url).href, - { - headersToMatch: { - // we are hitting the same page again and we most likely will see - // CDN hit (in this case Next reported cache status is omitted - // as it didn't actually take place in handling this request) - // or we will see CDN miss because different CDN node handled request - 'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m, - }, - headersNotMatchedMessage: - 'Second request to tested page (implicit locale html) should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', - }, - ) - const headers2ExplicitLocale = response2ExplicitLocale?.headers() || {} - expect(response2ExplicitLocale?.status()).toBe(200) - expect(headers2ExplicitLocale['x-nextjs-cache']).toBeUndefined() - if (!headers2ExplicitLocale['cache-status'].includes('"Netlify Edge"; hit')) { - // if we missed CDN cache, we will see Next cache hit status - // as we reuse cached response - expect(headers2ExplicitLocale['cache-status']).toMatch(/"Next.js"; hit/m) - } - expect(headers2ExplicitLocale['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - // the page is cached - const date2ExplicitLocale = await page.textContent('[data-testid="date-now"]') - expect(date2ExplicitLocale).toBe(date1ExplicitLocale) - - // check json route - const response2Json = await pollUntilHeadersMatch( - new URL( - 'base/path/_next/data/build-id/en/static/revalidate-manual.json', - pageRouterBasePathI18n.url, - ).href, - { - headersToMatch: { - // we are hitting the same page again and we most likely will see - // CDN hit (in this case Next reported cache status is omitted - // as it didn't actually take place in handling this request) - // or we will see CDN miss because different CDN node handled request - 'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m, - }, - headersNotMatchedMessage: - 'Second request to tested page (data) should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', - }, - ) - const headers2Json = response2Json?.headers() || {} - expect(response2Json?.status()).toBe(200) - if (!headers2Json['cache-status'].includes('"Netlify Edge"; hit')) { - // if we missed CDN cache, we will see Next cache hit status - // as we reuse cached response - expect(headers2Json['cache-status']).toMatch(/"Next.js"; hit/m) - } - expect(headers2Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - const data2 = (await response2Json?.json()) || {} - expect(data2?.pageProps?.time).toBe(date1ImplicitLocale) - - // revalidate implicit locale path - const revalidateImplicit = await page.goto( - new URL( - '/base/path/api/revalidate?path=/static/revalidate-manual', - pageRouterBasePathI18n.url, - ).href, - ) - expect(revalidateImplicit?.status()).toBe(200) - - // wait a bit until the page got regenerated - await page.waitForTimeout(1000) - - // now after the revalidation it should have a different date - const response3ImplicitLocale = await pollUntilHeadersMatch( - new URL('base/path/static/revalidate-manual', pageRouterBasePathI18n.url).href, - { - headersToMatch: { - // revalidate refreshes Next cache, but not CDN cache - // so our request after revalidation means that Next cache is already - // warmed up with fresh response, but CDN cache just knows that previously - // cached response is stale, so we are hitting our function that serve - // already cached response - 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], - }, - headersNotMatchedMessage: - 'Third request to tested page (implicit locale html) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', - }, - ) - const headers3ImplicitLocale = response3ImplicitLocale?.headers() || {} - expect(response3ImplicitLocale?.status()).toBe(200) - expect(headers3ImplicitLocale?.['x-nextjs-cache']).toBeUndefined() - - // the page has now an updated date - const date3ImplicitLocale = await page.textContent('[data-testid="date-now"]') - expect(date3ImplicitLocale).not.toBe(date2ImplicitLocale) - - const response3ExplicitLocale = await pollUntilHeadersMatch( - new URL('base/path/en/static/revalidate-manual', pageRouterBasePathI18n.url).href, - { - headersToMatch: { - // revalidate refreshes Next cache, but not CDN cache - // so our request after revalidation means that Next cache is already - // warmed up with fresh response, but CDN cache just knows that previously - // cached response is stale, so we are hitting our function that serve - // already cached response - 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], - }, - headersNotMatchedMessage: - 'Third request to tested page (explicit locale html) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', - }, - ) - const headers3ExplicitLocale = response3ExplicitLocale?.headers() || {} - expect(response3ExplicitLocale?.status()).toBe(200) - expect(headers3ExplicitLocale?.['x-nextjs-cache']).toBeUndefined() - - // the page has now an updated date - const date3ExplicitLocale = await page.textContent('[data-testid="date-now"]') - expect(date3ExplicitLocale).not.toBe(date2ExplicitLocale) - - // implicit and explicit locale paths should be the same (same cached response) - expect(date3ImplicitLocale).toBe(date3ExplicitLocale) - - // check json route - const response3Json = await pollUntilHeadersMatch( - new URL( - 'base/path/_next/data/build-id/en/static/revalidate-manual.json', - pageRouterBasePathI18n.url, - ).href, - { - headersToMatch: { - // revalidate refreshes Next cache, but not CDN cache - // so our request after revalidation means that Next cache is already - // warmed up with fresh response, but CDN cache just knows that previously - // cached response is stale, so we are hitting our function that serve - // already cached response - 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], - }, - headersNotMatchedMessage: - 'Third request to tested page (data) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', - }, - ) - const headers3Json = response3Json?.headers() || {} - expect(response3Json?.status()).toBe(200) - expect(headers3Json['x-nextjs-cache']).toBeUndefined() - if (!headers3Json['cache-status'].includes('"Netlify Edge"; hit')) { - // if we missed CDN cache, we will see Next cache hit status - // as we reuse cached response - expect(headers3Json['cache-status']).toMatch(/"Next.js"; hit/m) - } - expect(headers3Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - const data3 = (await response3Json?.json()) || {} - expect(data3?.pageProps?.time).toBe(date3ImplicitLocale) - - // revalidate implicit locale path - const revalidateExplicit = await page.goto( - new URL( - '/base/path/api/revalidate?path=/en/static/revalidate-manual', - pageRouterBasePathI18n.url, - ).href, - ) - expect(revalidateExplicit?.status()).toBe(200) - - // wait a bit until the page got regenerated - await page.waitForTimeout(1000) - - // now after the revalidation it should have a different date - const response4ImplicitLocale = await pollUntilHeadersMatch( - new URL('base/path/static/revalidate-manual', pageRouterBasePathI18n.url).href, - { - headersToMatch: { - // revalidate refreshes Next cache, but not CDN cache - // so our request after revalidation means that Next cache is already - // warmed up with fresh response, but CDN cache just knows that previously - // cached response is stale, so we are hitting our function that serve - // already cached response - 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], - }, - headersNotMatchedMessage: - 'Fourth request to tested page (implicit locale html) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', - }, - ) - const headers4ImplicitLocale = response4ImplicitLocale?.headers() || {} - expect(response4ImplicitLocale?.status()).toBe(200) - expect(headers4ImplicitLocale?.['x-nextjs-cache']).toBeUndefined() - - // the page has now an updated date - const date4ImplicitLocale = await page.textContent('[data-testid="date-now"]') - expect(date4ImplicitLocale).not.toBe(date3ImplicitLocale) - - const response4ExplicitLocale = await pollUntilHeadersMatch( - new URL('base/path/en/static/revalidate-manual', pageRouterBasePathI18n.url).href, - { - headersToMatch: { - // revalidate refreshes Next cache, but not CDN cache - // so our request after revalidation means that Next cache is already - // warmed up with fresh response, but CDN cache just knows that previously - // cached response is stale, so we are hitting our function that serve - // already cached response - 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], - }, - headersNotMatchedMessage: - 'Fourth request to tested page (explicit locale html) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', - }, - ) - const headers4ExplicitLocale = response4ExplicitLocale?.headers() || {} - expect(response4ExplicitLocale?.status()).toBe(200) - expect(headers4ExplicitLocale?.['x-nextjs-cache']).toBeUndefined() - - // the page has now an updated date - const date4ExplicitLocale = await page.textContent('[data-testid="date-now"]') - expect(date4ExplicitLocale).not.toBe(date3ExplicitLocale) - - // implicit and explicit locale paths should be the same (same cached response) - expect(date4ImplicitLocale).toBe(date4ExplicitLocale) - - // check json route - const response4Json = await pollUntilHeadersMatch( - new URL( - 'base/path/_next/data/build-id/en/static/revalidate-manual.json', - pageRouterBasePathI18n.url, - ).href, - { - headersToMatch: { - // revalidate refreshes Next cache, but not CDN cache - // so our request after revalidation means that Next cache is already - // warmed up with fresh response, but CDN cache just knows that previously - // cached response is stale, so we are hitting our function that serve - // already cached response - 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], - }, - headersNotMatchedMessage: - 'Fourth request to tested page (data) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', - }, - ) - const headers4Json = response4Json?.headers() || {} - expect(response4Json?.status()).toBe(200) - expect(headers4Json['x-nextjs-cache']).toBeUndefined() - expect(headers4Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - const data4 = (await response4Json?.json()) || {} - expect(data4?.pageProps?.time).toBe(date4ImplicitLocale) - }) - - test('non-default locale', async ({ page, pollUntilHeadersMatch, pageRouterBasePathI18n }) => { - // in case there is retry or some other test did hit that path before - // we want to make sure that cdn cache is not warmed up - const purgeCdnCache = await page.goto( - new URL( - '/base/path/api/purge-cdn?path=/de/static/revalidate-manual', - pageRouterBasePathI18n.url, - ).href, - ) - expect(purgeCdnCache?.status()).toBe(200) - - // wait a bit until cdn cache purge propagates - await page.waitForTimeout(500) - - const response1 = await pollUntilHeadersMatch( - new URL('/base/path/de/static/revalidate-manual', pageRouterBasePathI18n.url).href, - { - headersToMatch: { - // either first time hitting this route or we invalidated - // just CDN node in earlier step - // we will invoke function and see Next cache hit status \ - // in the response because it was prerendered at build time - // or regenerated in previous attempt to run this test - 'cache-status': [/"Netlify Edge"; fwd=(miss|stale)/m, /"Next.js"; hit/m], - }, - headersNotMatchedMessage: - 'First request to tested page (html) should be a miss or stale on the Edge and hit in Next.js', - }, - ) - const headers1 = response1?.headers() || {} - expect(response1?.status()).toBe(200) - expect(headers1['x-nextjs-cache']).toBeUndefined() - expect(headers1['netlify-cache-tag']).toBe('_n_t_/de/static/revalidate-manual') - expect(headers1['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - const date1 = await page.textContent('[data-testid="date-now"]') - const h1 = await page.textContent('h1') - expect(h1).toBe('Show #71') - - // check json route - const response1Json = await pollUntilHeadersMatch( - new URL( - 'base/path/_next/data/build-id/de/static/revalidate-manual.json', - pageRouterBasePathI18n.url, - ).href, - { - headersToMatch: { - // either first time hitting this route or we invalidated - // just CDN node in earlier step - // we will invoke function and see Next cache hit status \ - // in the response because it was prerendered at build time - // or regenerated in previous attempt to run this test - 'cache-status': [/"Netlify Edge"; fwd=(miss|stale)/m, /"Next.js"; hit/m], - }, - headersNotMatchedMessage: - 'First request to tested page (data) should be a miss or stale on the Edge and hit in Next.js', - }, - ) - const headers1Json = response1Json?.headers() || {} - expect(response1Json?.status()).toBe(200) - expect(headers1Json['x-nextjs-cache']).toBeUndefined() - expect(headers1Json['netlify-cache-tag']).toBe('_n_t_/de/static/revalidate-manual') - expect(headers1Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - const data1 = (await response1Json?.json()) || {} - expect(data1?.pageProps?.time).toBe(date1) - - const response2 = await pollUntilHeadersMatch( - new URL('base/path/de/static/revalidate-manual', pageRouterBasePathI18n.url).href, - { - headersToMatch: { - // we are hitting the same page again and we most likely will see - // CDN hit (in this case Next reported cache status is omitted - // as it didn't actually take place in handling this request) - // or we will see CDN miss because different CDN node handled request - 'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m, - }, - headersNotMatchedMessage: - 'Second request to tested page (html) should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', - }, - ) - const headers2 = response2?.headers() || {} - expect(response2?.status()).toBe(200) - expect(headers2['x-nextjs-cache']).toBeUndefined() - if (!headers2['cache-status'].includes('"Netlify Edge"; hit')) { - // if we missed CDN cache, we will see Next cache hit status - // as we reuse cached response - expect(headers2['cache-status']).toMatch(/"Next.js"; hit/m) - } - expect(headers2['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - // the page is cached - const date2 = await page.textContent('[data-testid="date-now"]') - expect(date2).toBe(date1) - - // check json route - const response2Json = await pollUntilHeadersMatch( - new URL( - 'base/path/_next/data/build-id/de/static/revalidate-manual.json', - pageRouterBasePathI18n.url, - ).href, - { - headersToMatch: { - // we are hitting the same page again and we most likely will see - // CDN hit (in this case Next reported cache status is omitted - // as it didn't actually take place in handling this request) - // or we will see CDN miss because different CDN node handled request - 'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m, - }, - headersNotMatchedMessage: - 'Second request to tested page (data) should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', - }, - ) - const headers2Json = response2Json?.headers() || {} - expect(response2Json?.status()).toBe(200) - expect(headers2Json['x-nextjs-cache']).toBeUndefined() - if (!headers2Json['cache-status'].includes('"Netlify Edge"; hit')) { - // if we missed CDN cache, we will see Next cache hit status - // as we reuse cached response - expect(headers2Json['cache-status']).toMatch(/"Next.js"; hit/m) - } - expect(headers2Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - const data2 = (await response2Json?.json()) || {} - expect(data2?.pageProps?.time).toBe(date1) - - const revalidate = await page.goto( - new URL( - '/base/path/api/revalidate?path=/de/static/revalidate-manual', - pageRouterBasePathI18n.url, - ).href, - ) - expect(revalidate?.status()).toBe(200) - - // wait a bit until the page got regenerated - await page.waitForTimeout(1000) - - // now after the revalidation it should have a different date - const response3 = await pollUntilHeadersMatch( - new URL('base/path/de/static/revalidate-manual', pageRouterBasePathI18n.url).href, - { - headersToMatch: { - // revalidate refreshes Next cache, but not CDN cache - // so our request after revalidation means that Next cache is already - // warmed up with fresh response, but CDN cache just knows that previously - // cached response is stale, so we are hitting our function that serve - // already cached response - 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], - }, - headersNotMatchedMessage: - 'Third request to tested page (html) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', - }, - ) - const headers3 = response3?.headers() || {} - expect(response3?.status()).toBe(200) - expect(headers3?.['x-nextjs-cache']).toBeUndefined() - - // the page has now an updated date - const date3 = await page.textContent('[data-testid="date-now"]') - expect(date3).not.toBe(date2) - - // check json route - const response3Json = await pollUntilHeadersMatch( - new URL( - 'base/path/_next/data/build-id/de/static/revalidate-manual.json', - pageRouterBasePathI18n.url, - ).href, - { - headersToMatch: { - // revalidate refreshes Next cache, but not CDN cache - // so our request after revalidation means that Next cache is already - // warmed up with fresh response, but CDN cache just knows that previously - // cached response is stale, so we are hitting our function that serve - // already cached response - 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], - }, - headersNotMatchedMessage: - 'Third request to tested page (data) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', - }, - ) - const headers3Json = response3Json?.headers() || {} - expect(response3Json?.status()).toBe(200) - expect(headers3Json['x-nextjs-cache']).toBeUndefined() - if (!headers3Json['cache-status'].includes('"Netlify Edge"; hit')) { - // if we missed CDN cache, we will see Next cache hit status - // as we reuse cached response - expect(headers3Json['cache-status']).toMatch(/"Next.js"; hit/m) - } - expect(headers3Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - const data3 = (await response3Json?.json()) || {} - expect(data3?.pageProps?.time).toBe(date3) - }) + for (const { label, prerendered, pagePath, expectedH1Content } of [ + { + label: 'prerendered page with static path', + prerendered: true, + pagePath: '/static/revalidate-manual', + expectedH1Content: 'Show #71', + }, + { + label: 'prerendered page with dynamic path', + prerendered: true, + pagePath: '/products/prerendered', + expectedH1Content: 'Product prerendered', + }, + { + label: 'not prerendered page with dynamic path', + prerendered: false, + pagePath: '/products/not-prerendered', + expectedH1Content: 'Product not-prerendered', + }, + ]) { + test.describe(label, () => { + test(`default locale`, async ({ page, pollUntilHeadersMatch, pageRouterBasePathI18n }) => { + // in case there is retry or some other test did hit that path before + // we want to make sure that cdn cache is not warmed up + const purgeCdnCache = await page.goto( + new URL(`/base/path/api/purge-cdn?path=/en${pagePath}`, pageRouterBasePathI18n.url) + .href, + ) + expect(purgeCdnCache?.status()).toBe(200) + + // wait a bit until cdn cache purge propagates + await page.waitForTimeout(500) + + const response1ImplicitLocale = await pollUntilHeadersMatch( + new URL(`base/path${pagePath}`, pageRouterBasePathI18n.url).href, + { + headersToMatch: { + // either first time hitting this route or we invalidated + // just CDN node in earlier step + // we will invoke function and see Next cache hit status + // in the response because it was prerendered at build time + // or regenerated in previous attempt to run this test + 'cache-status': [ + /"Netlify Edge"; fwd=(miss|stale)/m, + prerendered ? /"Next.js"; hit/m : /"Next.js"; (hit|fwd=miss)/m, + ], + }, + headersNotMatchedMessage: + 'First request to tested page (implicit locale html) should be a miss or stale on the Edge and hit in Next.js', + }, + ) + const headers1ImplicitLocale = response1ImplicitLocale?.headers() || {} + expect(response1ImplicitLocale?.status()).toBe(200) + expect(headers1ImplicitLocale['x-nextjs-cache']).toBeUndefined() + expect(headers1ImplicitLocale['netlify-cache-tag']).toBe(`_n_t_/en${pagePath}`) + expect(headers1ImplicitLocale['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + const date1ImplicitLocale = await page.textContent('[data-testid="date-now"]') + const h1ImplicitLocale = await page.textContent('h1') + expect(h1ImplicitLocale).toBe(expectedH1Content) + + const response1ExplicitLocale = await pollUntilHeadersMatch( + new URL(`base/path/en${pagePath}`, pageRouterBasePathI18n.url).href, + { + headersToMatch: { + // either first time hitting this route or we invalidated + // just CDN node in earlier step + // we will invoke function and see Next cache hit status \ + // in the response because it was set by previous request that didn't have locale in pathname + 'cache-status': [/"Netlify Edge"; fwd=(miss|stale)/m, /"Next.js"; hit/m], + }, + headersNotMatchedMessage: + 'First request to tested page (explicit locale html) should be a miss or stale on the Edge and hit in Next.js', + }, + ) + const headers1ExplicitLocale = response1ExplicitLocale?.headers() || {} + expect(response1ExplicitLocale?.status()).toBe(200) + expect(headers1ExplicitLocale['x-nextjs-cache']).toBeUndefined() + expect(headers1ExplicitLocale['netlify-cache-tag']).toBe(`_n_t_/en${pagePath}`) + expect(headers1ExplicitLocale['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + const date1ExplicitLocale = await page.textContent('[data-testid="date-now"]') + const h1ExplicitLocale = await page.textContent('h1') + expect(h1ExplicitLocale).toBe(expectedH1Content) + + // implicit and explicit locale paths should be the same (same cached response) + expect(date1ImplicitLocale).toBe(date1ExplicitLocale) + + // check json route + const response1Json = await pollUntilHeadersMatch( + new URL(`base/path/_next/data/build-id/en${pagePath}.json`, pageRouterBasePathI18n.url) + .href, + { + headersToMatch: { + // either first time hitting this route or we invalidated + // just CDN node in earlier step + // we will invoke function and see Next cache hit status \ + // in the response because it was prerendered at build time + // or regenerated in previous attempt to run this test + 'cache-status': [/"Netlify Edge"; fwd=(miss|stale)/m, /"Next.js"; hit/m], + }, + headersNotMatchedMessage: + 'First request to tested page (data) should be a miss or stale on the Edge and hit in Next.js', + }, + ) + const headers1Json = response1Json?.headers() || {} + expect(response1Json?.status()).toBe(200) + expect(headers1Json['x-nextjs-cache']).toBeUndefined() + expect(headers1Json['netlify-cache-tag']).toBe(`_n_t_/en${pagePath}`) + expect(headers1Json['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + const data1 = (await response1Json?.json()) || {} + expect(data1?.pageProps?.time).toBe(date1ImplicitLocale) + + const response2ImplicitLocale = await pollUntilHeadersMatch( + new URL(`base/path${pagePath}`, pageRouterBasePathI18n.url).href, + { + headersToMatch: { + // we are hitting the same page again and we most likely will see + // CDN hit (in this case Next reported cache status is omitted + // as it didn't actually take place in handling this request) + // or we will see CDN miss because different CDN node handled request + 'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m, + }, + headersNotMatchedMessage: + 'Second request to tested page (implicit locale html) should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', + }, + ) + const headers2ImplicitLocale = response2ImplicitLocale?.headers() || {} + expect(response2ImplicitLocale?.status()).toBe(200) + expect(headers2ImplicitLocale['x-nextjs-cache']).toBeUndefined() + if (!headers2ImplicitLocale['cache-status'].includes('"Netlify Edge"; hit')) { + // if we missed CDN cache, we will see Next cache hit status + // as we reuse cached response + expect(headers2ImplicitLocale['cache-status']).toMatch(/"Next.js"; hit/m) + } + expect(headers2ImplicitLocale['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + // the page is cached + const date2ImplicitLocale = await page.textContent('[data-testid="date-now"]') + expect(date2ImplicitLocale).toBe(date1ImplicitLocale) + + const response2ExplicitLocale = await pollUntilHeadersMatch( + new URL(`base/path${pagePath}`, pageRouterBasePathI18n.url).href, + { + headersToMatch: { + // we are hitting the same page again and we most likely will see + // CDN hit (in this case Next reported cache status is omitted + // as it didn't actually take place in handling this request) + // or we will see CDN miss because different CDN node handled request + 'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m, + }, + headersNotMatchedMessage: + 'Second request to tested page (implicit locale html) should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', + }, + ) + const headers2ExplicitLocale = response2ExplicitLocale?.headers() || {} + expect(response2ExplicitLocale?.status()).toBe(200) + expect(headers2ExplicitLocale['x-nextjs-cache']).toBeUndefined() + if (!headers2ExplicitLocale['cache-status'].includes('"Netlify Edge"; hit')) { + // if we missed CDN cache, we will see Next cache hit status + // as we reuse cached response + expect(headers2ExplicitLocale['cache-status']).toMatch(/"Next.js"; hit/m) + } + expect(headers2ExplicitLocale['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + // the page is cached + const date2ExplicitLocale = await page.textContent('[data-testid="date-now"]') + expect(date2ExplicitLocale).toBe(date1ExplicitLocale) + + // check json route + const response2Json = await pollUntilHeadersMatch( + new URL(`base/path/_next/data/build-id/en${pagePath}.json`, pageRouterBasePathI18n.url) + .href, + { + headersToMatch: { + // we are hitting the same page again and we most likely will see + // CDN hit (in this case Next reported cache status is omitted + // as it didn't actually take place in handling this request) + // or we will see CDN miss because different CDN node handled request + 'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m, + }, + headersNotMatchedMessage: + 'Second request to tested page (data) should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', + }, + ) + const headers2Json = response2Json?.headers() || {} + expect(response2Json?.status()).toBe(200) + if (!headers2Json['cache-status'].includes('"Netlify Edge"; hit')) { + // if we missed CDN cache, we will see Next cache hit status + // as we reuse cached response + expect(headers2Json['cache-status']).toMatch(/"Next.js"; hit/m) + } + expect(headers2Json['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + const data2 = (await response2Json?.json()) || {} + expect(data2?.pageProps?.time).toBe(date1ImplicitLocale) + + // revalidate implicit locale path + const revalidateImplicit = await page.goto( + new URL(`/base/path/api/revalidate?path=${pagePath}`, pageRouterBasePathI18n.url).href, + ) + expect(revalidateImplicit?.status()).toBe(200) + + // wait a bit until the page got regenerated + await page.waitForTimeout(1000) + + // now after the revalidation it should have a different date + const response3ImplicitLocale = await pollUntilHeadersMatch( + new URL(`base/path${pagePath}`, pageRouterBasePathI18n.url).href, + { + headersToMatch: { + // revalidate refreshes Next cache, but not CDN cache + // so our request after revalidation means that Next cache is already + // warmed up with fresh response, but CDN cache just knows that previously + // cached response is stale, so we are hitting our function that serve + // already cached response + 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], + }, + headersNotMatchedMessage: + 'Third request to tested page (implicit locale html) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', + }, + ) + const headers3ImplicitLocale = response3ImplicitLocale?.headers() || {} + expect(response3ImplicitLocale?.status()).toBe(200) + expect(headers3ImplicitLocale?.['x-nextjs-cache']).toBeUndefined() + + // the page has now an updated date + const date3ImplicitLocale = await page.textContent('[data-testid="date-now"]') + expect(date3ImplicitLocale).not.toBe(date2ImplicitLocale) + + const response3ExplicitLocale = await pollUntilHeadersMatch( + new URL(`base/path/en${pagePath}`, pageRouterBasePathI18n.url).href, + { + headersToMatch: { + // revalidate refreshes Next cache, but not CDN cache + // so our request after revalidation means that Next cache is already + // warmed up with fresh response, but CDN cache just knows that previously + // cached response is stale, so we are hitting our function that serve + // already cached response + 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], + }, + headersNotMatchedMessage: + 'Third request to tested page (explicit locale html) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', + }, + ) + const headers3ExplicitLocale = response3ExplicitLocale?.headers() || {} + expect(response3ExplicitLocale?.status()).toBe(200) + expect(headers3ExplicitLocale?.['x-nextjs-cache']).toBeUndefined() + + // the page has now an updated date + const date3ExplicitLocale = await page.textContent('[data-testid="date-now"]') + expect(date3ExplicitLocale).not.toBe(date2ExplicitLocale) + + // implicit and explicit locale paths should be the same (same cached response) + expect(date3ImplicitLocale).toBe(date3ExplicitLocale) + + // check json route + const response3Json = await pollUntilHeadersMatch( + new URL(`base/path/_next/data/build-id/en${pagePath}.json`, pageRouterBasePathI18n.url) + .href, + { + headersToMatch: { + // revalidate refreshes Next cache, but not CDN cache + // so our request after revalidation means that Next cache is already + // warmed up with fresh response, but CDN cache just knows that previously + // cached response is stale, so we are hitting our function that serve + // already cached response + 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], + }, + headersNotMatchedMessage: + 'Third request to tested page (data) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', + }, + ) + const headers3Json = response3Json?.headers() || {} + expect(response3Json?.status()).toBe(200) + expect(headers3Json['x-nextjs-cache']).toBeUndefined() + if (!headers3Json['cache-status'].includes('"Netlify Edge"; hit')) { + // if we missed CDN cache, we will see Next cache hit status + // as we reuse cached response + expect(headers3Json['cache-status']).toMatch(/"Next.js"; hit/m) + } + expect(headers3Json['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + const data3 = (await response3Json?.json()) || {} + expect(data3?.pageProps?.time).toBe(date3ImplicitLocale) + + // revalidate implicit locale path + const revalidateExplicit = await page.goto( + new URL(`/base/path/api/revalidate?path=/en${pagePath}`, pageRouterBasePathI18n.url) + .href, + ) + expect(revalidateExplicit?.status()).toBe(200) + + // wait a bit until the page got regenerated + await page.waitForTimeout(1000) + + // now after the revalidation it should have a different date + const response4ImplicitLocale = await pollUntilHeadersMatch( + new URL(`base/path${pagePath}`, pageRouterBasePathI18n.url).href, + { + headersToMatch: { + // revalidate refreshes Next cache, but not CDN cache + // so our request after revalidation means that Next cache is already + // warmed up with fresh response, but CDN cache just knows that previously + // cached response is stale, so we are hitting our function that serve + // already cached response + 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], + }, + headersNotMatchedMessage: + 'Fourth request to tested page (implicit locale html) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', + }, + ) + const headers4ImplicitLocale = response4ImplicitLocale?.headers() || {} + expect(response4ImplicitLocale?.status()).toBe(200) + expect(headers4ImplicitLocale?.['x-nextjs-cache']).toBeUndefined() + + // the page has now an updated date + const date4ImplicitLocale = await page.textContent('[data-testid="date-now"]') + expect(date4ImplicitLocale).not.toBe(date3ImplicitLocale) + + const response4ExplicitLocale = await pollUntilHeadersMatch( + new URL(`base/path/en${pagePath}`, pageRouterBasePathI18n.url).href, + { + headersToMatch: { + // revalidate refreshes Next cache, but not CDN cache + // so our request after revalidation means that Next cache is already + // warmed up with fresh response, but CDN cache just knows that previously + // cached response is stale, so we are hitting our function that serve + // already cached response + 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], + }, + headersNotMatchedMessage: + 'Fourth request to tested page (explicit locale html) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', + }, + ) + const headers4ExplicitLocale = response4ExplicitLocale?.headers() || {} + expect(response4ExplicitLocale?.status()).toBe(200) + expect(headers4ExplicitLocale?.['x-nextjs-cache']).toBeUndefined() + + // the page has now an updated date + const date4ExplicitLocale = await page.textContent('[data-testid="date-now"]') + expect(date4ExplicitLocale).not.toBe(date3ExplicitLocale) + + // implicit and explicit locale paths should be the same (same cached response) + expect(date4ImplicitLocale).toBe(date4ExplicitLocale) + + // check json route + const response4Json = await pollUntilHeadersMatch( + new URL(`base/path/_next/data/build-id/en${pagePath}.json`, pageRouterBasePathI18n.url) + .href, + { + headersToMatch: { + // revalidate refreshes Next cache, but not CDN cache + // so our request after revalidation means that Next cache is already + // warmed up with fresh response, but CDN cache just knows that previously + // cached response is stale, so we are hitting our function that serve + // already cached response + 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], + }, + headersNotMatchedMessage: + 'Fourth request to tested page (data) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', + }, + ) + const headers4Json = response4Json?.headers() || {} + expect(response4Json?.status()).toBe(200) + expect(headers4Json['x-nextjs-cache']).toBeUndefined() + expect(headers4Json['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + const data4 = (await response4Json?.json()) || {} + expect(data4?.pageProps?.time).toBe(date4ImplicitLocale) + }) + + test('non-default locale', async ({ + page, + pollUntilHeadersMatch, + pageRouterBasePathI18n, + }) => { + // in case there is retry or some other test did hit that path before + // we want to make sure that cdn cache is not warmed up + const purgeCdnCache = await page.goto( + new URL(`/base/path/api/purge-cdn?path=/de${pagePath}`, pageRouterBasePathI18n.url) + .href, + ) + expect(purgeCdnCache?.status()).toBe(200) + + // wait a bit until cdn cache purge propagates + await page.waitForTimeout(500) + + const response1 = await pollUntilHeadersMatch( + new URL(`/base/path/de${pagePath}`, pageRouterBasePathI18n.url).href, + { + headersToMatch: { + // either first time hitting this route or we invalidated + // just CDN node in earlier step + // we will invoke function and see Next cache hit status + // in the response because it was prerendered at build time + // or regenerated in previous attempt to run this test + 'cache-status': [ + /"Netlify Edge"; fwd=(miss|stale)/m, + prerendered ? /"Next.js"; hit/m : /"Next.js"; (hit|fwd=miss)/m, + ], + }, + headersNotMatchedMessage: + 'First request to tested page (html) should be a miss or stale on the Edge and hit in Next.js', + }, + ) + const headers1 = response1?.headers() || {} + expect(response1?.status()).toBe(200) + expect(headers1['x-nextjs-cache']).toBeUndefined() + expect(headers1['netlify-cache-tag']).toBe(`_n_t_/de${pagePath}`) + expect(headers1['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + const date1 = await page.textContent('[data-testid="date-now"]') + const h1 = await page.textContent('h1') + expect(h1).toBe(expectedH1Content) + + // check json route + const response1Json = await pollUntilHeadersMatch( + new URL(`base/path/_next/data/build-id/de${pagePath}.json`, pageRouterBasePathI18n.url) + .href, + { + headersToMatch: { + // either first time hitting this route or we invalidated + // just CDN node in earlier step + // we will invoke function and see Next cache hit status \ + // in the response because it was prerendered at build time + // or regenerated in previous attempt to run this test + 'cache-status': [/"Netlify Edge"; fwd=(miss|stale)/m, /"Next.js"; hit/m], + }, + headersNotMatchedMessage: + 'First request to tested page (data) should be a miss or stale on the Edge and hit in Next.js', + }, + ) + const headers1Json = response1Json?.headers() || {} + expect(response1Json?.status()).toBe(200) + expect(headers1Json['x-nextjs-cache']).toBeUndefined() + expect(headers1Json['netlify-cache-tag']).toBe(`_n_t_/de${pagePath}`) + expect(headers1Json['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + const data1 = (await response1Json?.json()) || {} + expect(data1?.pageProps?.time).toBe(date1) + + const response2 = await pollUntilHeadersMatch( + new URL(`base/path/de${pagePath}`, pageRouterBasePathI18n.url).href, + { + headersToMatch: { + // we are hitting the same page again and we most likely will see + // CDN hit (in this case Next reported cache status is omitted + // as it didn't actually take place in handling this request) + // or we will see CDN miss because different CDN node handled request + 'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m, + }, + headersNotMatchedMessage: + 'Second request to tested page (html) should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', + }, + ) + const headers2 = response2?.headers() || {} + expect(response2?.status()).toBe(200) + expect(headers2['x-nextjs-cache']).toBeUndefined() + if (!headers2['cache-status'].includes('"Netlify Edge"; hit')) { + // if we missed CDN cache, we will see Next cache hit status + // as we reuse cached response + expect(headers2['cache-status']).toMatch(/"Next.js"; hit/m) + } + expect(headers2['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + // the page is cached + const date2 = await page.textContent('[data-testid="date-now"]') + expect(date2).toBe(date1) + + // check json route + const response2Json = await pollUntilHeadersMatch( + new URL(`base/path/_next/data/build-id/de${pagePath}.json`, pageRouterBasePathI18n.url) + .href, + { + headersToMatch: { + // we are hitting the same page again and we most likely will see + // CDN hit (in this case Next reported cache status is omitted + // as it didn't actually take place in handling this request) + // or we will see CDN miss because different CDN node handled request + 'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m, + }, + headersNotMatchedMessage: + 'Second request to tested page (data) should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', + }, + ) + const headers2Json = response2Json?.headers() || {} + expect(response2Json?.status()).toBe(200) + expect(headers2Json['x-nextjs-cache']).toBeUndefined() + if (!headers2Json['cache-status'].includes('"Netlify Edge"; hit')) { + // if we missed CDN cache, we will see Next cache hit status + // as we reuse cached response + expect(headers2Json['cache-status']).toMatch(/"Next.js"; hit/m) + } + expect(headers2Json['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + const data2 = (await response2Json?.json()) || {} + expect(data2?.pageProps?.time).toBe(date1) + + const revalidate = await page.goto( + new URL(`/base/path/api/revalidate?path=/de${pagePath}`, pageRouterBasePathI18n.url) + .href, + ) + expect(revalidate?.status()).toBe(200) + + // wait a bit until the page got regenerated + await page.waitForTimeout(1000) + + // now after the revalidation it should have a different date + const response3 = await pollUntilHeadersMatch( + new URL(`base/path/de${pagePath}`, pageRouterBasePathI18n.url).href, + { + headersToMatch: { + // revalidate refreshes Next cache, but not CDN cache + // so our request after revalidation means that Next cache is already + // warmed up with fresh response, but CDN cache just knows that previously + // cached response is stale, so we are hitting our function that serve + // already cached response + 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], + }, + headersNotMatchedMessage: + 'Third request to tested page (html) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', + }, + ) + const headers3 = response3?.headers() || {} + expect(response3?.status()).toBe(200) + expect(headers3?.['x-nextjs-cache']).toBeUndefined() + + // the page has now an updated date + const date3 = await page.textContent('[data-testid="date-now"]') + expect(date3).not.toBe(date2) + + // check json route + const response3Json = await pollUntilHeadersMatch( + new URL(`base/path/_next/data/build-id/de${pagePath}.json`, pageRouterBasePathI18n.url) + .href, + { + headersToMatch: { + // revalidate refreshes Next cache, but not CDN cache + // so our request after revalidation means that Next cache is already + // warmed up with fresh response, but CDN cache just knows that previously + // cached response is stale, so we are hitting our function that serve + // already cached response + 'cache-status': [/"Next.js"; hit/m, /"Netlify Edge"; fwd=(miss|stale)/m], + }, + headersNotMatchedMessage: + 'Third request to tested page (data) should be a miss or stale on the Edge and hit in Next.js after on-demand revalidation', + }, + ) + const headers3Json = response3Json?.headers() || {} + expect(response3Json?.status()).toBe(200) + expect(headers3Json['x-nextjs-cache']).toBeUndefined() + if (!headers3Json['cache-status'].includes('"Netlify Edge"; hit')) { + // if we missed CDN cache, we will see Next cache hit status + // as we reuse cached response + expect(headers3Json['cache-status']).toMatch(/"Next.js"; hit/m) + } + expect(headers3Json['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + const data3 = (await response3Json?.json()) || {} + expect(data3?.pageProps?.time).toBe(date3) + }) + }) + } }) test('requesting a non existing page route that needs to be fetched from the blob store like 404.html', async ({ diff --git a/tests/fixtures/page-router-base-path-i18n/pages/products/[slug].js b/tests/fixtures/page-router-base-path-i18n/pages/products/[slug].js new file mode 100644 index 0000000000..4c5efa662e --- /dev/null +++ b/tests/fixtures/page-router-base-path-i18n/pages/products/[slug].js @@ -0,0 +1,40 @@ +const Product = ({ time, slug }) => ( +
+

Product {slug}

+

+ This page uses getStaticProps() and getStaticPaths() to pre-fetch a Product + {time} +

+
+) + +export async function getStaticProps({ params }) { + return { + props: { + time: new Date().toISOString(), + slug: params.slug, + }, + } +} + +export const getStaticPaths = () => { + return { + paths: [ + { + params: { + slug: 'prerendered', + }, + locale: 'en', + }, + { + params: { + slug: 'prerendered', + }, + locale: 'de', + }, + ], + fallback: 'blocking', // false or "blocking" + } +} + +export default Product diff --git a/tests/fixtures/page-router/pages/api/revalidate.js b/tests/fixtures/page-router/pages/api/revalidate.js index 79c2428b51..e134a56577 100644 --- a/tests/fixtures/page-router/pages/api/revalidate.js +++ b/tests/fixtures/page-router/pages/api/revalidate.js @@ -1,6 +1,7 @@ export default async function handler(req, res) { try { - await res.revalidate('/static/revalidate-manual') + const pathToPurge = req.query.path ?? '/static/revalidate-manual' + await res.revalidate(pathToPurge) return res.json({ code: 200, message: 'success' }) } catch (err) { return res.status(500).send({ code: 500, message: err.message }) diff --git a/tests/fixtures/page-router/pages/products/[slug].js b/tests/fixtures/page-router/pages/products/[slug].js index 709182e0a8..47b24654ba 100644 --- a/tests/fixtures/page-router/pages/products/[slug].js +++ b/tests/fixtures/page-router/pages/products/[slug].js @@ -1,5 +1,6 @@ -const Product = ({ time }) => ( +const Product = ({ time, slug }) => (
+

Product {slug}

This page uses getStaticProps() and getStaticPaths() to pre-fetch a Product {time} @@ -7,10 +8,11 @@ const Product = ({ time }) => (

) -export async function getStaticProps() { +export async function getStaticProps({ params }) { return { props: { time: new Date().toISOString(), + slug: params.slug, }, } } @@ -23,8 +25,13 @@ export const getStaticPaths = () => { slug: 'an-incredibly-long-product-name-thats-impressively-repetetively-needlessly-overdimensioned-and-should-be-shortened-to-less-than-255-characters-for-the-sake-of-seo-and-ux-and-first-and-foremost-for-gods-sake-but-nobody-wont-ever-read-this-anyway', }, }, + { + params: { + slug: 'prerendered', + }, + }, ], - fallback: false, // false or "blocking" + fallback: 'blocking', // false or "blocking" } } diff --git a/tests/integration/static.test.ts b/tests/integration/static.test.ts index 44f5443f4c..14ded6c943 100644 --- a/tests/integration/static.test.ts +++ b/tests/integration/static.test.ts @@ -35,6 +35,7 @@ test('requesting a non existing page route that needs to be const entries = await getBlobEntries(ctx) expect(entries.map(({ key }) => decodeBlobKey(key.substring(0, 50))).sort()).toEqual([ '/products/an-incredibly-long-product-', + '/products/prerendered', '/static/revalidate-automatic', '/static/revalidate-manual', '/static/revalidate-slow',