Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: hard-navigate when preflight request fails #35145

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/next/server/dev/next-dev-server.ts
Expand Up @@ -540,9 +540,16 @@ export default class DevServer extends Server {
return result
} catch (error) {
this.logErrorWithOriginalStack(error, undefined, 'client')

const preflight =
params.request.method === 'HEAD' &&
params.request.headers['x-middleware-preflight']
if (preflight) throw error

const err = getProperError(error)
;(err as any).middleware = true
const { request, response, parsedUrl } = params
response.statusCode = 500
this.renderError(err, request, response, parsedUrl.pathname)
return null
}
Expand Down
19 changes: 14 additions & 5 deletions packages/next/shared/lib/router/router.ts
Expand Up @@ -1880,11 +1880,20 @@ export default class Router implements BaseRouter {
return { type: 'next' }
}

const preflight = await this._getPreflightData({
preflightHref: options.as,
shouldCache: options.cache,
isPreview: options.isPreview,
})
let preflight: PreflightData | undefined
try {
preflight = await this._getPreflightData({
preflightHref: options.as,
shouldCache: options.cache,
isPreview: options.isPreview,
})
} catch (err) {
// If preflight request fails, we need to do a hard-navigation.
return {
type: 'redirect',
destination: options.as,
}
}

if (preflight.rewrite) {
// for external rewrites we need to do a hard navigation
Expand Down
14 changes: 14 additions & 0 deletions test/integration/middleware/core/pages/errors/_middleware.js
@@ -0,0 +1,14 @@
import { NextResponse } from 'next/server'

export async function middleware(request) {
const url = request.nextUrl

if (
url.pathname === '/errors/throw-on-preflight' &&
request.headers.has('x-middleware-preflight')
) {
throw new Error('test error')
}

return NextResponse.next()
}
11 changes: 11 additions & 0 deletions test/integration/middleware/core/pages/errors/index.js
@@ -0,0 +1,11 @@
import Link from 'next/link'

export default function Errors() {
return (
<div>
<Link href="/errors/throw-on-preflight?message=refreshed">
<a id="throw-on-preflight">Throw on preflight</a>
</Link>
</div>
)
}
@@ -0,0 +1,12 @@
export default function ThrowOnPreflight({ message }) {
return (
<div>
<h1 className="title">Throw on preflight request</h1>
<p className={message}>{message}</p>
</div>
)
}

export const getServerSideProps = ({ query }) => ({
props: { message: query.message || '' },
})
14 changes: 14 additions & 0 deletions test/integration/middleware/core/test/index.test.js
Expand Up @@ -47,6 +47,8 @@ describe('Middleware base tests', () => {
interfaceTests('/fr')
urlTests(log)
urlTests(log, '/fr')
errorTests()
errorTests('/fr')

it('should have showed warning for middleware usage', () => {
expect(log.output).toContain(middlewareWarning)
Expand Down Expand Up @@ -84,6 +86,8 @@ describe('Middleware base tests', () => {
interfaceTests('/fr')
urlTests(serverOutput)
urlTests(serverOutput, '/fr')
errorTests()
errorTests('/fr')

it('should have middleware warning during build', () => {
expect(buildOutput).toContain(middlewareWarning)
Expand Down Expand Up @@ -798,6 +802,16 @@ function interfaceTests(locale = '') {
})
}

function errorTests(locale = '') {
it(`${locale} should hard-navigate when preflight request failed`, async () => {
const browser = await webdriver(context.appPort, `${locale}/errors`)
await browser.eval('window.__SAME_PAGE = true')
await browser.elementByCss('#throw-on-preflight').click()
await browser.waitForElementByCss('.refreshed')
expect(await browser.eval('window.__SAME_PAGE')).toBeUndefined()
})
}

function getCookieFromResponse(res, cookieName) {
// node-fetch bundles the cookies as string in the Response
const cookieArray = res.headers.raw()['set-cookie']
Expand Down