From 8fe6676905829de292ed9082348b7220f9fc5259 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 24 Jun 2024 15:52:25 +0200 Subject: [PATCH] fix: set netlify-cache-tag for not prerendered content (#2495) * test: refactor app router on-demand revalidation test and add test cases for not prerendered content * test: refactor pages router on-demand revalidation test and add test cases for not prerendered content * fix: capture cache tags during request handling and don't rely on tag manifest created for prerendered pages * Update src/run/handlers/cache.cts Co-authored-by: Philippe Serhal * chore: remove dead code --------- Co-authored-by: Philippe Serhal --- CONTRIBUTING.md | 1 - src/build/content/server.ts | 41 - src/build/functions/server.ts | 2 - src/run/config.ts | 6 - src/run/handlers/cache.cts | 43 + src/run/handlers/request-context.cts | 1 + src/run/handlers/server.ts | 17 +- src/run/headers.ts | 19 +- tests/e2e/on-demand-app.test.ts | 359 ++-- 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 +- .../api/on-demand-revalidate/path/route.ts | 5 +- .../app/api/on-demand-revalidate/tag/route.ts | 5 +- .../app/static-fetch/[id]/page.js | 6 +- tests/integration/cache-handler.test.ts | 1 + tests/integration/static.test.ts | 1 + tests/utils/helpers.ts | 15 - 19 files changed, 1023 insertions(+), 1087 deletions(-) create mode 100644 tests/fixtures/page-router-base-path-i18n/pages/products/[slug].js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 00fd94cb12..e4eecb7cb8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,6 @@ For a simple next.js app ``` /___netlify-server-handler ├── .netlify -│ ├── tags-manifest.json │ └── dist // the compiled runtime code │ └── run │ ├── handlers diff --git a/src/build/content/server.ts b/src/build/content/server.ts index dcf497cf4e..39e11ea8c6 100644 --- a/src/build/content/server.ts +++ b/src/build/content/server.ts @@ -311,47 +311,6 @@ export const copyNextDependencies = async (ctx: PluginContext): Promise => }) } -export const writeTagsManifest = async (ctx: PluginContext): Promise => { - const manifest = await ctx.getPrerenderManifest() - - const routes = Object.entries(manifest.routes).map(async ([route, definition]) => { - let tags - - // app router - if (definition.dataRoute?.endsWith('.rsc')) { - const path = join(ctx.publishDir, `server/app/${route === '/' ? '/index' : route}.meta`) - try { - const file = await readFile(path, 'utf-8') - const meta = JSON.parse(file) - tags = meta.headers['x-next-cache-tags'] - } catch { - // Parallel route default layout has no prerendered page, so don't warn about it - if (!definition.dataRoute?.endsWith('/default.rsc')) { - console.log(`Unable to read cache tags for: ${path}`) - } - } - } - - // pages router - if (definition.dataRoute?.endsWith('.json')) { - tags = `_N_T_${route}` - } - - // route handler - if (definition.dataRoute === null) { - tags = definition.initialHeaders?.['x-next-cache-tags'] - } - - return [route, tags] - }) - - await writeFile( - join(ctx.serverHandlerDir, '.netlify/tags-manifest.json'), - JSON.stringify(Object.fromEntries(await Promise.all(routes))), - 'utf-8', - ) -} - /** * Generates a copy of the middleware manifest without any middleware in it. We * do this because we'll run middleware in an edge function, and we don't want diff --git a/src/build/functions/server.ts b/src/build/functions/server.ts index 39716a1ace..e34997ee06 100644 --- a/src/build/functions/server.ts +++ b/src/build/functions/server.ts @@ -10,7 +10,6 @@ import { copyNextDependencies, copyNextServerCode, verifyHandlerDirStructure, - writeTagsManifest, } from '../content/server.js' import { PluginContext, SERVER_HANDLER_NAME } from '../plugin-context.js' @@ -138,7 +137,6 @@ export const createServerHandler = async (ctx: PluginContext) => { await copyNextServerCode(ctx) await copyNextDependencies(ctx) - await writeTagsManifest(ctx) await copyHandlerDependencies(ctx) await writeHandlerManifest(ctx) await writePackageMetadata(ctx) diff --git a/src/run/config.ts b/src/run/config.ts index bd3a4e2b06..9ca3f3dc5b 100644 --- a/src/run/config.ts +++ b/src/run/config.ts @@ -38,9 +38,3 @@ export const setRunConfig = (config: NextConfigComplete) => { // set config process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(config) } - -export type TagsManifest = Record - -export const getTagsManifest = async (): Promise => { - return JSON.parse(await readFile(resolve(PLUGIN_DIR, '.netlify/tags-manifest.json'), 'utf-8')) -} diff --git a/src/run/handlers/cache.cts b/src/run/handlers/cache.cts index 3dbec7208a..3a0a194bc0 100644 --- a/src/run/handlers/cache.cts +++ b/src/run/handlers/cache.cts @@ -109,6 +109,44 @@ export class NetlifyCacheHandler implements CacheHandler { return restOfRouteValue } + private captureCacheTags(cacheValue: NetlifyIncrementalCacheValue | null, key: string) { + if (!cacheValue) { + return + } + + const requestContext = getRequestContext() + // Bail if we can't get request context + if (!requestContext) { + return + } + + // Bail if we already have cache tags - `captureCacheTags()` is called on both `CacheHandler.get` and `CacheHandler.set` + // that's because `CacheHandler.get` might not have a cache value (cache miss or on-demand revalidation) in which case + // response is generated in blocking way and we need to capture cache tags from the cache value we are setting. + // If both `CacheHandler.get` and `CacheHandler.set` are called in the same request, we want to use cache tags from + // first `CacheHandler.get` and not from following `CacheHandler.set` as this is pattern for Stale-while-revalidate behavior + // and stale response is served while new one is generated. + if (requestContext.responseCacheTags) { + return + } + + if ( + cacheValue.kind === 'PAGE' || + cacheValue.kind === 'APP_PAGE' || + cacheValue.kind === 'ROUTE' + ) { + if (cacheValue.headers?.[NEXT_CACHE_TAGS_HEADER]) { + const cacheTags = (cacheValue.headers[NEXT_CACHE_TAGS_HEADER] as string).split(',') + requestContext.responseCacheTags = cacheTags + } else if (cacheValue.kind === 'PAGE' && typeof cacheValue.pageData === 'object') { + // pages router doesn't have cache tags headers in PAGE cache value + // so we need to generate appropriate cache tags for it + const cacheTags = [`_N_T_${key === '/index' ? '/' : key}`] + requestContext.responseCacheTags = cacheTags + } + } + } + private async injectEntryToPrerenderManifest( key: string, revalidate: NetlifyCachedPageValue['revalidate'], @@ -176,6 +214,7 @@ export class NetlifyCacheHandler implements CacheHandler { } this.captureResponseCacheLastModified(blob, key, span) + this.captureCacheTags(blob.value, key) switch (blob.value?.kind) { case 'FETCH': @@ -273,6 +312,10 @@ export class NetlifyCacheHandler implements CacheHandler { const value = this.transformToStorableObject(data, context) + // if previous CacheHandler.get call returned null (page was either never rendered or was on-demand revalidated) + // and we didn't yet capture cache tags, we try to get cache tags from freshly produced cache value + this.captureCacheTags(value, key) + await this.blobStore.setJSON(blobKey, { lastModified, value, diff --git a/src/run/handlers/request-context.cts b/src/run/handlers/request-context.cts index 22bdfd123d..c54427f74a 100644 --- a/src/run/handlers/request-context.cts +++ b/src/run/handlers/request-context.cts @@ -10,6 +10,7 @@ export type RequestContext = { captureServerTiming: boolean responseCacheGetLastModified?: number responseCacheKey?: string + responseCacheTags?: string[] usedFsRead?: boolean didPagesRouterOnDemandRevalidate?: boolean serverTiming?: string diff --git a/src/run/handlers/server.ts b/src/run/handlers/server.ts index c63cfcd42d..d7dd4c5c0a 100644 --- a/src/run/handlers/server.ts +++ b/src/run/handlers/server.ts @@ -5,7 +5,6 @@ import { Context } from '@netlify/functions' import type { NextConfigComplete } from 'next/dist/server/config-shared.js' import type { WorkerRequestHandler } from 'next/dist/server/lib/types.js' -import { getTagsManifest, TagsManifest } from '../config.js' import { adjustDateHeader, setCacheControlHeaders, @@ -20,7 +19,7 @@ import { getTracer } from './tracer.cjs' const nextImportPromise = import('../next.cjs') -let nextHandler: WorkerRequestHandler, nextConfig: NextConfigComplete, tagsManifest: TagsManifest +let nextHandler: WorkerRequestHandler, nextConfig: NextConfigComplete /** * When Next.js proxies requests externally, it writes the response back as-is. @@ -55,21 +54,11 @@ export default async (request: Request, context: FutureContext) => { const tracer = getTracer() if (!nextHandler) { - await tracer.withActiveSpan('initialize next server', async (span) => { + await tracer.withActiveSpan('initialize next server', async () => { // set the server config const { getRunConfig, setRunConfig } = await import('../config.js') nextConfig = await getRunConfig() setRunConfig(nextConfig) - tagsManifest = await getTagsManifest() - span.setAttributes( - Object.entries(tagsManifest).reduce( - (acc, [key, value]) => { - acc[`tagsManifest.${key}`] = value - return acc - }, - {} as Record, - ), - ) const { getMockedRequestHandlers } = await nextImportPromise const url = new URL(request.url) @@ -124,7 +113,7 @@ export default async (request: Request, context: FutureContext) => { await adjustDateHeader({ headers: response.headers, request, span, tracer, requestContext }) setCacheControlHeaders(response.headers, request, requestContext) - setCacheTagsHeaders(response.headers, request, tagsManifest, requestContext) + setCacheTagsHeaders(response.headers, requestContext) setVaryHeaders(response.headers, request, nextConfig) setCacheStatusHeader(response.headers) diff --git a/src/run/headers.ts b/src/run/headers.ts index 53b0137074..a5b80cc13b 100644 --- a/src/run/headers.ts +++ b/src/run/headers.ts @@ -3,7 +3,6 @@ import type { NextConfigComplete } from 'next/dist/server/config-shared.js' import { encodeBlobKey } from '../shared/blobkey.js' -import type { TagsManifest } from './config.js' import type { RequestContext } from './handlers/request-context.cjs' import type { RuntimeTracer } from './handlers/tracer.cjs' import { getRegionalBlobStore } from './regional-blob-store.cjs' @@ -275,21 +274,9 @@ export const setCacheControlHeaders = ( } } -function getCanonicalPathFromCacheKey(cacheKey: string | undefined): string | undefined { - return cacheKey === '/index' ? '/' : cacheKey -} - -export const setCacheTagsHeaders = ( - headers: Headers, - request: Request, - manifest: TagsManifest, - requestContext: RequestContext, -) => { - const path = - getCanonicalPathFromCacheKey(requestContext.responseCacheKey) ?? new URL(request.url).pathname - const tags = manifest[path] - if (tags !== undefined) { - headers.set('netlify-cache-tag', tags) +export const setCacheTagsHeaders = (headers: Headers, requestContext: RequestContext) => { + if (requestContext.responseCacheTags) { + headers.set('netlify-cache-tag', requestContext.responseCacheTags.join(',')) } } diff --git a/tests/e2e/on-demand-app.test.ts b/tests/e2e/on-demand-app.test.ts index 47f411df4a..85b1fb69dd 100644 --- a/tests/e2e/on-demand-app.test.ts +++ b/tests/e2e/on-demand-app.test.ts @@ -2,47 +2,89 @@ import { expect } from '@playwright/test' import { test } from '../utils/playwright-helpers.js' test.describe('app router on-demand revalidation', () => { - test('revalidatePath', async ({ page, pollUntilHeadersMatch, serverComponents }) => { - // 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-fetch/1', serverComponents.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-fetch/1', serverComponents.url).href, - { + for (const { label, prerendered, pagePath, revalidateApiPath, expectedH1Content } of [ + { + label: 'revalidatePath (prerendered page with static path)', + prerendered: true, + pagePath: '/static-fetch-1', + revalidateApiPath: '/api/on-demand-revalidate/path?path=/static-fetch-1', + expectedH1Content: 'Hello, Static Fetch 1', + }, + { + label: 'revalidateTag (prerendered page with static path)', + prerendered: true, + pagePath: '/static-fetch-2', + revalidateApiPath: '/api/on-demand-revalidate/tag?tag=collection', + expectedH1Content: 'Hello, Static Fetch 2', + }, + { + label: 'revalidatePath (prerendered page with dynamic path)', + prerendered: true, + pagePath: '/static-fetch/1', + revalidateApiPath: '/api/on-demand-revalidate/path?path=/static-fetch/1', + expectedH1Content: 'Hello, Statically fetched show 1', + }, + { + label: 'revalidateTag (prerendered page with dynamic path)', + prerendered: true, + pagePath: '/static-fetch/2', + revalidateApiPath: '/api/on-demand-revalidate/tag?tag=show-2', + expectedH1Content: 'Hello, Statically fetched show 2', + }, + { + label: 'revalidatePath (not prerendered page with dynamic path)', + prerendered: false, + pagePath: '/static-fetch/3', + revalidateApiPath: '/api/on-demand-revalidate/path?path=/static-fetch/3', + expectedH1Content: 'Hello, Statically fetched show 3', + }, + { + label: 'revalidateTag (not prerendered page with dynamic path)', + prerendered: false, + pagePath: '/static-fetch/4', + revalidateApiPath: '/api/on-demand-revalidate/tag?tag=show-4', + expectedH1Content: 'Hello, Statically fetched show 4', + }, + ]) { + test(label, async ({ page, pollUntilHeadersMatch, serverComponents }) => { + // 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}`, serverComponents.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, serverComponents.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 + // we will invoke function and see Next cache hit status + // in the response if 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], + '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 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-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) + }) + const headers1 = response1?.headers() || {} + expect(response1?.status()).toBe(200) + expect(headers1['x-nextjs-cache']).toBeUndefined() + expect(headers1['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) - const date1 = await page.textContent('[data-testid="date-now"]') + const date1 = await page.textContent('[data-testid="date-now"]') - const h1 = await page.textContent('h1') - expect(h1).toBe('Hello, Statically fetched show 1') + const h1 = await page.textContent('h1') + expect(h1).toBe(expectedH1Content) - const response2 = await pollUntilHeadersMatch( - new URL('static-fetch/1', serverComponents.url).href, - { + const response2 = await pollUntilHeadersMatch(new URL(pagePath, serverComponents.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 @@ -52,36 +94,31 @@ test.describe('app router on-demand revalidation', () => { }, headersNotMatchedMessage: 'Second request to tested page 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) - - const revalidate = await page.goto( - new URL('/api/on-demand-revalidate/path', serverComponents.url).href, - ) - expect(revalidate?.status()).toBe(200) - - // wait a bit until cdn tags and invalidated and cdn is purged - await page.waitForTimeout(500) - - // now after the revalidation it should have a different date - const response3 = await pollUntilHeadersMatch( - new URL('static-fetch/1', serverComponents.url).href, - { + }) + 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) + + const revalidate = await page.goto(new URL(revalidateApiPath, serverComponents.url).href) + expect(revalidate?.status()).toBe(200) + + // wait a bit until cdn tags and invalidated and cdn is purged + await page.waitForTimeout(500) + + // now after the revalidation it should have a different date + const response3 = await pollUntilHeadersMatch(new URL(pagePath, serverComponents.url).href, { headersToMatch: { // revalidatePath just marks the page(s) as invalid and does NOT // automatically refreshes the cache. This request will cause @@ -92,22 +129,19 @@ test.describe('app router on-demand revalidation', () => { }, headersNotMatchedMessage: 'Third request to tested page should be a miss or stale on the Edge and miss in Next.js after on-demand revalidation', - }, - ) - const headers3 = response3?.headers() || {} - expect(response3?.status()).toBe(200) - expect(headers3?.['x-nextjs-cache']).toBeUndefined() - expect(headers3['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - // the page has now an updated date - const date3 = await page.textContent('[data-testid="date-now"]') - expect(date3).not.toBe(date2) - - const response4 = await pollUntilHeadersMatch( - new URL('static-fetch/1', serverComponents.url).href, - { + }) + const headers3 = response3?.headers() || {} + expect(response3?.status()).toBe(200) + expect(headers3?.['x-nextjs-cache']).toBeUndefined() + expect(headers3['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + // the page has now an updated date + const date3 = await page.textContent('[data-testid="date-now"]') + expect(date3).not.toBe(date2) + + const response4 = await pollUntilHeadersMatch(new URL(pagePath, serverComponents.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 @@ -117,157 +151,22 @@ test.describe('app router on-demand revalidation', () => { }, headersNotMatchedMessage: 'Fourth request to tested page should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', - }, - ) - const headers4 = response4?.headers() || {} - expect(response4?.status()).toBe(200) - expect(headers4?.['x-nextjs-cache']).toBeUndefined() - if (!headers4['cache-status'].includes('"Netlify Edge"; hit')) { - // if we missed CDN cache, we will see Next cache hit status - // as we reuse cached response - expect(headers4['cache-status']).toMatch(/"Next.js"; hit/m) - } - expect(headers4['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - // the page is cached - const date4 = await page.textContent('[data-testid="date-now"]') - expect(date4).toBe(date3) - }) - - test('revalidateTag', async ({ page, pollUntilHeadersMatch, serverComponents }) => { - // 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-fetch-1', serverComponents.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-fetch-1', serverComponents.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 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-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('Hello, Static Fetch 1') - - const response2 = await pollUntilHeadersMatch( - new URL('static-fetch-1', serverComponents.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 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) - - const revalidate = await page.goto( - new URL('/api/on-demand-revalidate/tag', serverComponents.url).href, - ) - expect(revalidate?.status()).toBe(200) - - // wait a bit until cdn tags and invalidated and cdn is purged - await page.waitForTimeout(500) - - // now after the revalidation it should have a different date - const response3 = await pollUntilHeadersMatch( - new URL('static-fetch-1', serverComponents.url).href, - { - headersToMatch: { - // revalidateTag just marks the page(s) as invalid and does NOT - // automatically refreshes the cache. This request will cause - // Next.js cache miss and new response will be generated and cached - // Depending if we hit same CDN node as previous request, we might - // get either fwd=miss or fwd=stale - 'cache-status': [/"Next.js"; fwd=miss/m, /"Netlify Edge"; fwd=(miss|stale)/m], - }, - headersNotMatchedMessage: - 'Third request to tested page should be a miss or stale on the Edge and miss in Next.js after on-demand revalidation', - }, - ) - const headers3 = response3?.headers() || {} - expect(response3?.status()).toBe(200) - expect(headers3?.['x-nextjs-cache']).toBeUndefined() - expect(headers3['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - // the page has now an updated date - const date3 = await page.textContent('[data-testid="date-now"]') - expect(date3).not.toBe(date2) - - const response4 = await pollUntilHeadersMatch( - new URL('static-fetch-1', serverComponents.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: - 'Fourth request to tested page should most likely be a hit on the Edge (optionally miss or stale if different CDN node)', - }, - ) - const headers4 = response4?.headers() || {} - expect(response4?.status()).toBe(200) - expect(headers4?.['x-nextjs-cache']).toBeUndefined() - - if (!headers4['cache-status'].includes('"Netlify Edge"; hit')) { - // if we missed CDN cache, we will see Next cache hit status - // as we reuse cached response - expect(headers4['cache-status']).toMatch(/"Next.js"; hit/m) - } - expect(headers4['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000', - ) - - // the page is cached - const date4 = await page.textContent('[data-testid="date-now"]') - expect(date4).toBe(date3) - }) + }) + const headers4 = response4?.headers() || {} + expect(response4?.status()).toBe(200) + expect(headers4?.['x-nextjs-cache']).toBeUndefined() + if (!headers4['cache-status'].includes('"Netlify Edge"; hit')) { + // if we missed CDN cache, we will see Next cache hit status + // as we reuse cached response + expect(headers4['cache-status']).toMatch(/"Next.js"; hit/m) + } + expect(headers4['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) + + // the page is cached + const date4 = await page.textContent('[data-testid="date-now"]') + expect(date4).toBe(date3) + }) + } }) 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/fixtures/server-components/app/api/on-demand-revalidate/path/route.ts b/tests/fixtures/server-components/app/api/on-demand-revalidate/path/route.ts index 249b132235..4da6b30321 100644 --- a/tests/fixtures/server-components/app/api/on-demand-revalidate/path/route.ts +++ b/tests/fixtures/server-components/app/api/on-demand-revalidate/path/route.ts @@ -2,7 +2,10 @@ import { NextRequest, NextResponse } from 'next/server' import { revalidatePath } from 'next/cache' export async function GET(request: NextRequest) { - revalidatePath('/static-fetch/[id]', 'page') + const url = new URL(request.url) + const pathToRevalidate = url.searchParams.get('path') ?? '/static-fetch/[id]/page' + + revalidatePath(pathToRevalidate) return NextResponse.json({ revalidated: true, now: new Date().toISOString() }) } diff --git a/tests/fixtures/server-components/app/api/on-demand-revalidate/tag/route.ts b/tests/fixtures/server-components/app/api/on-demand-revalidate/tag/route.ts index 613b951b00..ae91b90b4e 100644 --- a/tests/fixtures/server-components/app/api/on-demand-revalidate/tag/route.ts +++ b/tests/fixtures/server-components/app/api/on-demand-revalidate/tag/route.ts @@ -2,7 +2,10 @@ import { NextRequest, NextResponse } from 'next/server' import { revalidateTag } from 'next/cache' export async function GET(request: NextRequest) { - revalidateTag('collection') + const url = new URL(request.url) + const tagToRevalidate = url.searchParams.get('tag') ?? 'collection' + + revalidateTag(tagToRevalidate) return NextResponse.json({ revalidated: true, now: new Date().toISOString() }) } diff --git a/tests/fixtures/server-components/app/static-fetch/[id]/page.js b/tests/fixtures/server-components/app/static-fetch/[id]/page.js index c8ccaea3c1..24e1abe815 100644 --- a/tests/fixtures/server-components/app/static-fetch/[id]/page.js +++ b/tests/fixtures/server-components/app/static-fetch/[id]/page.js @@ -3,7 +3,11 @@ export async function generateStaticParams() { } async function getData(params) { - const res = await fetch(`https://api.tvmaze.com/shows/${params.id}`) + const res = await fetch(`https://api.tvmaze.com/shows/${params.id}`, { + next: { + tags: [`show-${params.id}`], + }, + }) return res.json() } diff --git a/tests/integration/cache-handler.test.ts b/tests/integration/cache-handler.test.ts index a1945fa66a..afc308de24 100644 --- a/tests/integration/cache-handler.test.ts +++ b/tests/integration/cache-handler.test.ts @@ -45,6 +45,7 @@ describe('page router', () => { expect(blobEntries.map(({ key }) => decodeBlobKey(key.substring(0, 50))).sort()).toEqual([ // the real key is much longer and ends in a hash, but we only assert on the first 50 chars to make it easier '/products/an-incredibly-long-product-', + '/products/prerendered', '/static/revalidate-automatic', '/static/revalidate-manual', '/static/revalidate-slow', 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', diff --git a/tests/utils/helpers.ts b/tests/utils/helpers.ts index 6664bdd05b..c9d6e4ae1a 100644 --- a/tests/utils/helpers.ts +++ b/tests/utils/helpers.ts @@ -3,7 +3,6 @@ import getPort from 'get-port' import { getDeployStore } from '@netlify/blobs' import { BlobsServer } from '@netlify/blobs/server' import type { NetlifyPluginUtils } from '@netlify/build' -import IncrementalCache from 'next/dist/server/lib/incremental-cache/index.js' import { Buffer } from 'node:buffer' import { mkdtemp } from 'node:fs/promises' import { tmpdir } from 'node:os' @@ -12,20 +11,6 @@ import { assert, vi } from 'vitest' import { BLOB_TOKEN } from './constants' import { type FixtureTestContext } from './contexts' -/** - * Uses next.js incremental cache to compute the same cache key for a URL that is automatically generated - * This is needed for mocking out fetch calls to test them - */ -export const getFetchCacheKey = async (url: string) => { - const incCache = new IncrementalCache.IncrementalCache({ - requestHeaders: {}, - getPrerenderManifest: () => ({}), - } as any) - - const key = await incCache.fetchCacheKey(url) - return key -} - /** * Generates a 24char deploy ID (this is validated in the blob storage so we cant use a uuidv4) * @returns