diff --git a/packages/next/src/shared/lib/router/router.ts b/packages/next/src/shared/lib/router/router.ts index 5e4ffceaee7fa..19be0fd09deef 100644 --- a/packages/next/src/shared/lib/router/router.ts +++ b/packages/next/src/shared/lib/router/router.ts @@ -588,7 +588,11 @@ function fetchNextData({ // without blocking navigation when stale data is available if (unstable_skipClientCache && persistCache) { return getData({}).then((data) => { - inflightCache[cacheKey] = Promise.resolve(data) + if (data.response.headers.get('x-middleware-cache') !== 'no-cache') { + // only update cache if not marked as no-cache + inflightCache[cacheKey] = Promise.resolve(data) + } + return data }) } diff --git a/test/e2e/middleware-rewrites/app/middleware.js b/test/e2e/middleware-rewrites/app/middleware.js index 4990063415110..493e3734e15ad 100644 --- a/test/e2e/middleware-rewrites/app/middleware.js +++ b/test/e2e/middleware-rewrites/app/middleware.js @@ -109,6 +109,19 @@ export async function middleware(request) { return NextResponse.rewrite(url) } + if (url.pathname === '/dynamic-no-cache/1') { + const rewriteUrl = + request.headers.get('purpose') === 'prefetch' + ? '/dynamic-no-cache/1' + : '/dynamic-no-cache/2' + + url.pathname = rewriteUrl + + return NextResponse.rewrite(url, { + headers: { 'x-middleware-cache': 'no-cache' }, + }) + } + if ( url.pathname === '/rewrite-me-without-hard-navigation' || url.searchParams.get('path') === 'rewrite-me-without-hard-navigation' diff --git a/test/e2e/middleware-rewrites/app/pages/dynamic-no-cache/[id].js b/test/e2e/middleware-rewrites/app/pages/dynamic-no-cache/[id].js new file mode 100644 index 0000000000000..6727cd6f22584 --- /dev/null +++ b/test/e2e/middleware-rewrites/app/pages/dynamic-no-cache/[id].js @@ -0,0 +1,22 @@ +export default function Dynamic({ id }) { + return ( +
+

Page {id}

+
+ ) +} + +export const getStaticProps = async ({ params }) => { + return { + props: { + id: params.id, + }, + } +} + +export const getStaticPaths = async () => { + return { + paths: [{ params: { id: '1' } }, { params: { id: '2' } }], + fallback: false, + } +} diff --git a/test/e2e/middleware-rewrites/app/pages/index.js b/test/e2e/middleware-rewrites/app/pages/index.js index 2e3d07c7ec261..a3cd14e1e3e15 100644 --- a/test/e2e/middleware-rewrites/app/pages/index.js +++ b/test/e2e/middleware-rewrites/app/pages/index.js @@ -66,6 +66,7 @@ export default function Home() { normal SSG link + /dynamic-no-cache/1
{ let next: NextInstance @@ -363,6 +363,36 @@ describe('Middleware Rewrite', () => { }) if (!(global as any).isNextDev) { + it('should opt out of prefetch caching for dynamic routes', async () => { + const browser = await webdriver(next.url, '/') + await browser.eval('window.__SAME_PAGE = true') + await browser.waitForIdleNetwork() + let hasResolvedPrefetch = false + + browser.on('response', async (res: Response) => { + const req = res.request() + const headers = await req.allHeaders() + if ( + headers['purpose'] === 'prefetch' && + req.url().includes('/dynamic-no-cache/1') + ) { + hasResolvedPrefetch = true + } + }) + + await browser.elementByCss("[href='/dynamic-no-cache/1']").moveTo() + + await retry(async () => { + expect(hasResolvedPrefetch).toBe(true) + }) + + await browser.elementByCss("[href='/dynamic-no-cache/1']").click() + + await browser.waitForElementByCss('#dynamic-page') + expect(await browser.elementById('dynamic-page').text()).toBe('Page 2') + expect(await browser.eval('window.__SAME_PAGE')).toBe(true) + }) + it('should not prefetch non-SSG routes', async () => { const browser = await webdriver(next.url, '/')