Skip to content

Commit

Permalink
fix: hard-navigate when preflight request fails (#35145)
Browse files Browse the repository at this point in the history
fixes #34199

Also fixes response code to return `500` when showing error page of preflight request failure on dev mode

## Bug

- [x] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`
  • Loading branch information
nkzawa committed Mar 8, 2022
1 parent d3cd00c commit 9e4724d
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 5 deletions.
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

0 comments on commit 9e4724d

Please sign in to comment.