diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index f841a9c4e00c1..10f6d0f52702b 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -82,6 +82,7 @@ import ResponseCache from '../server/response-cache' import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-slash' import { getNextPathnameInfo } from '../shared/lib/router/utils/get-next-pathname-info' import { bodyStreamToNodeStream, clonableBodyForRequest } from './body-streams' +import { checkIsManualRevalidate } from './api-utils' const shouldUseReactRoot = parseInt(React.version) >= 18 if (shouldUseReactRoot) { @@ -1137,6 +1138,14 @@ export default class NextNodeServer extends BaseServer { onWarning?: (warning: Error) => void }) { middlewareBetaWarning() + + // middleware is skipped for on-demand revalidate requests + if ( + checkIsManualRevalidate(params.request, this.renderOpts.previewProps) + .isManualRevalidate + ) { + return { finished: false } + } const normalizedPathname = removeTrailingSlash(params.parsed.pathname || '') // For middleware to "fetch" we must always provide an absolute URL diff --git a/test/e2e/middleware-general/app/middleware.js b/test/e2e/middleware-general/app/middleware.js index bb43465315563..215d8b090ba91 100644 --- a/test/e2e/middleware-general/app/middleware.js +++ b/test/e2e/middleware-general/app/middleware.js @@ -36,6 +36,12 @@ const params = (url) => { export async function middleware(request) { const url = request.nextUrl + if (request.headers.get('x-prerender-revalidate')) { + const res = NextResponse.next() + res.headers.set('x-middleware', 'hi') + return res + } + // this is needed for tests to get the BUILD_ID if (url.pathname.startsWith('/_next/static/__BUILD_ID')) { return NextResponse.next() diff --git a/test/e2e/middleware-general/app/pages/ssg/[slug].js b/test/e2e/middleware-general/app/pages/ssg/[slug].js new file mode 100644 index 0000000000000..621575a122f1f --- /dev/null +++ b/test/e2e/middleware-general/app/pages/ssg/[slug].js @@ -0,0 +1,30 @@ +import { useRouter } from 'next/router' + +export default function Page(props) { + const router = useRouter() + return ( + <> +

/blog/[slug]

+

{JSON.stringify(router.query)}

+

{router.pathname}

+

{router.asPath}

+

{JSON.stringify(props)}

+ + ) +} + +export function getStaticProps({ params }) { + return { + props: { + now: Date.now(), + params, + }, + } +} + +export function getStaticPaths() { + return { + paths: ['/ssg/first'], + fallback: 'blocking', + } +} diff --git a/test/e2e/middleware-general/test/index.test.ts b/test/e2e/middleware-general/test/index.test.ts index e8647008d7eaf..049b0e90015b0 100644 --- a/test/e2e/middleware-general/test/index.test.ts +++ b/test/e2e/middleware-general/test/index.test.ts @@ -116,6 +116,21 @@ describe('Middleware Runtime', () => { ) } }) + + it('should not run middleware for on-demand revalidate', async () => { + const bypassToken = ( + await fs.readJSON(join(next.testDir, '.next/prerender-manifest.json')) + ).preview.previewModeId + + const res = await fetchViaHTTP(next.url, '/ssg/first', undefined, { + headers: { + 'x-prerender-revalidate': bypassToken, + }, + }) + expect(res.status).toBe(200) + expect(res.headers.get('x-middleware')).toBeFalsy() + expect(res.headers.get('x-nextjs-cache')).toBe('REVALIDATED') + }) } it('should have correct dynamic route params on client-transition to dynamic route', async () => {