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

Ensure query is provided correctly with middleware rewrites #42818

Merged
merged 14 commits into from Nov 15, 2022
90 changes: 61 additions & 29 deletions packages/next/shared/lib/router/router.ts
Expand Up @@ -1415,6 +1415,16 @@ export default class Router implements BaseRouter {
? removeTrailingSlash(removeBasePath(pathname))
: pathname

let route = removeTrailingSlash(pathname)
const parsedAsPathname = as.startsWith('/') && parseRelativeUrl(as).pathname

const isMiddlewareRewrite = !!(
parsedAsPathname &&
route !== parsedAsPathname &&
(!isDynamicRoute(route) ||
!getRouteMatcher(getRouteRegex(route))(parsedAsPathname))
)

// we don't attempt resolve asPath when we need to execute
// middleware as the resolving will occur server-side
const isMiddlewareMatch = await matchesMiddleware({
Expand Down Expand Up @@ -1489,7 +1499,7 @@ export default class Router implements BaseRouter {

resolvedAs = removeLocale(removeBasePath(resolvedAs), nextState.locale)

let route = removeTrailingSlash(pathname)
route = removeTrailingSlash(pathname)
let routeMatch: { [paramName: string]: string | string[] } | false = false

if (isDynamicRoute(route)) {
Expand Down Expand Up @@ -1565,6 +1575,7 @@ export default class Router implements BaseRouter {
hasMiddleware: isMiddlewareMatch,
unstable_skipClientCache: options.unstable_skipClientCache,
isQueryUpdating: isQueryUpdating && !this.isFallback,
isMiddlewareRewrite,
})

if ('route' in routeInfo && isMiddlewareMatch) {
Expand Down Expand Up @@ -1913,6 +1924,7 @@ export default class Router implements BaseRouter {
isPreview,
unstable_skipClientCache,
isQueryUpdating,
isMiddlewareRewrite,
}: {
route: string
pathname: string
Expand All @@ -1925,6 +1937,7 @@ export default class Router implements BaseRouter {
isPreview: boolean
unstable_skipClientCache?: boolean
isQueryUpdating?: boolean
isMiddlewareRewrite?: boolean
}) {
/**
* This `route` binding can change if there's a rewrite
Expand Down Expand Up @@ -1970,16 +1983,26 @@ export default class Router implements BaseRouter {
isBackground: isQueryUpdating,
}

const data = isQueryUpdating
? ({} as any)
: await withMiddlewareEffects({
fetchData: () => fetchNextData(fetchNextDataParams),
asPath: resolvedAs,
locale: locale,
router: this,
})
const data =
isQueryUpdating && !isMiddlewareRewrite
? ({} as any)
: await withMiddlewareEffects({
fetchData: () => fetchNextData(fetchNextDataParams),
asPath: resolvedAs,
locale: locale,
router: this,
}).catch((err) => {
// we don't hard error during query updating
// as it's un-necessary and doesn't need to be fatal
// unless it is a fallback route and the props can't
// be loaded
if (isQueryUpdating) {
return {} as any
}
throw err
})

if (isQueryUpdating && data) {
if (isQueryUpdating) {
data.json = self.__NEXT_DATA__.props
}
handleCancelled()
Expand All @@ -1992,26 +2015,35 @@ export default class Router implements BaseRouter {
}

if (data?.effect?.type === 'rewrite') {
route = removeTrailingSlash(data.effect.resolvedHref)
pathname = data.effect.resolvedHref
query = { ...query, ...data.effect.parsedAs.query }
resolvedAs = removeBasePath(
normalizeLocalePath(data.effect.parsedAs.pathname, this.locales)
.pathname
)
const resolvedRoute = removeTrailingSlash(data.effect.resolvedHref)
const pages = await this.pageLoader.getPageList()

// during query updating the page must match although during
// client-transition a redirect that doesn't match a page
// can be returned and this should trigger a hard navigation
// which is valid for incremental migration
if (!isQueryUpdating || pages.includes(resolvedRoute)) {
route = resolvedRoute
pathname = data.effect.resolvedHref
query = { ...query, ...data.effect.parsedAs.query }
resolvedAs = removeBasePath(
normalizeLocalePath(data.effect.parsedAs.pathname, this.locales)
.pathname
)

// Check again the cache with the new destination.
existingInfo = this.components[route]
if (
routeProps.shallow &&
existingInfo &&
this.route === route &&
!hasMiddleware
) {
// If we have a match with the current route due to rewrite,
// we can copy the existing information to the rewritten one.
// Then, we return the information along with the matched route.
return { ...existingInfo, route }
// Check again the cache with the new destination.
existingInfo = this.components[route]
if (
routeProps.shallow &&
existingInfo &&
this.route === route &&
!hasMiddleware
) {
// If we have a match with the current route due to rewrite,
// we can copy the existing information to the rewritten one.
// Then, we return the information along with the matched route.
return { ...existingInfo, route }
}
}
}

Expand Down
15 changes: 12 additions & 3 deletions test/e2e/middleware-general/test/index.test.ts
Expand Up @@ -230,6 +230,7 @@ describe('Middleware Runtime', () => {
await check(() => browser.elementByCss('body').text(), /\/to-ssg/)

expect(JSON.parse(await browser.elementByCss('#query').text())).toEqual({
from: 'middleware',
slug: 'hello',
})
expect(
Expand Down Expand Up @@ -278,7 +279,11 @@ describe('Middleware Runtime', () => {
})

it('should have correct dynamic route params for middleware rewrite to dynamic route', async () => {
const browser = await webdriver(next.url, '/')
const browser = await webdriver(next.url, '/404')
await check(
() => browser.eval('next.router.isReady ? "yes" : "no"'),
'yes'
)
await browser.eval('window.beforeNav = 1')
await browser.eval('window.next.router.push("/rewrite-to-dynamic")')
await browser.waitForElementByCss('#blog')
Expand All @@ -301,7 +306,11 @@ describe('Middleware Runtime', () => {
})

it('should have correct route params for chained rewrite from middleware to config rewrite', async () => {
const browser = await webdriver(next.url, '/')
const browser = await webdriver(next.url, '/404')
await check(
() => browser.eval('next.router.isReady ? "yes" : "no"'),
'yes'
)
await browser.eval('window.beforeNav = 1')
await browser.eval(
'window.next.router.push("/rewrite-to-config-rewrite")'
Expand All @@ -327,7 +336,7 @@ describe('Middleware Runtime', () => {
})

it('should have correct route params for rewrite from config dynamic route', async () => {
const browser = await webdriver(next.url, '/')
const browser = await webdriver(next.url, '/404')
await browser.eval('window.beforeNav = 1')
await browser.eval('window.next.router.push("/rewrite-3")')
await browser.waitForElementByCss('#blog')
Expand Down
5 changes: 5 additions & 0 deletions test/e2e/middleware-rewrites/app/middleware.js
Expand Up @@ -19,6 +19,11 @@ export async function middleware(request) {
})
}

if (url.pathname.includes('/rewrite-to-static')) {
request.nextUrl.pathname = '/static-ssg/post-1'
return NextResponse.rewrite(request.nextUrl)
}

if (url.pathname.includes('/fallback-true-blog/rewritten')) {
request.nextUrl.pathname = '/about'
return NextResponse.rewrite(request.nextUrl)
Expand Down
4 changes: 4 additions & 0 deletions test/e2e/middleware-rewrites/app/next.config.js
Expand Up @@ -20,6 +20,10 @@ module.exports = {
source: '/afterfiles-rewrite-ssg',
destination: '/fallback-true-blog/first',
},
{
source: '/config-rewrite-to-dynamic-static/:rewriteSlug',
destination: '/ssg',
},
],
fallback: [],
}
Expand Down
14 changes: 14 additions & 0 deletions test/e2e/middleware-rewrites/app/pages/static-ssg/[slug].js
@@ -0,0 +1,14 @@
import { useRouter } from 'next/router'

export default function Page() {
const router = useRouter()

return (
<>
<p id="page">/static-ssg/[slug]</p>
<p id="query">{JSON.stringify(router.query)}</p>
<p id="pathname">{router.pathname}</p>
<p id="as-path">{router.asPath}</p>
</>
)
}
36 changes: 36 additions & 0 deletions test/e2e/middleware-rewrites/test/index.test.ts
Expand Up @@ -23,6 +23,42 @@ describe('Middleware Rewrite', () => {
})

function tests() {
it('should handle static dynamic rewrite from middleware correctly', async () => {
const browser = await webdriver(next.url, '/rewrite-to-static')

await check(() => browser.eval('next.router.query.slug'), 'post-1')
expect(await browser.elementByCss('#page').text()).toBe(
'/static-ssg/[slug]'
)
expect(JSON.parse(await browser.elementByCss('#query').text())).toEqual({
slug: 'post-1',
})
expect(await browser.elementByCss('#pathname').text()).toBe(
'/static-ssg/[slug]'
)
expect(await browser.elementByCss('#as-path').text()).toBe(
'/rewrite-to-static'
)
})

it('should handle static rewrite from next.config.js correctly', async () => {
const browser = await webdriver(
next.url,
'/config-rewrite-to-dynamic-static/post-2'
)

await check(() => browser.eval('next.router.query.rewriteSlug'), 'post-2')
expect(
JSON.parse(await browser.eval('JSON.stringify(next.router.query)'))
).toEqual({
rewriteSlug: 'post-2',
})
expect(await browser.eval('next.router.pathname')).toBe('/ssg')
expect(await browser.eval('next.router.asPath')).toBe(
'/config-rewrite-to-dynamic-static/post-2'
)
})

it('should not have un-necessary data request on rewrite', async () => {
const browser = await webdriver(next.url, '/to-blog/first', {
waitHydration: false,
Expand Down
23 changes: 20 additions & 3 deletions test/e2e/middleware-trailing-slash/test/index.test.ts
Expand Up @@ -120,6 +120,7 @@ describe('Middleware Runtime trailing slash', () => {
await check(() => browser.elementByCss('body').text(), /\/to-ssg/)

expect(JSON.parse(await browser.elementByCss('#query').text())).toEqual({
from: 'middleware',
slug: 'hello',
})
expect(
Expand All @@ -133,6 +134,10 @@ describe('Middleware Runtime trailing slash', () => {

it('should have correct dynamic route params on client-transition to dynamic route', async () => {
const browser = await webdriver(next.url, '/')
await check(
() => browser.eval('next.router.isReady ? "yes" : "no"'),
'yes'
)
await browser.eval('window.beforeNav = 1')
await browser.eval('window.next.router.push("/blog/first")')
await browser.waitForElementByCss('#blog')
Expand Down Expand Up @@ -170,7 +175,11 @@ describe('Middleware Runtime trailing slash', () => {
})

it('should have correct dynamic route params for middleware rewrite to dynamic route', async () => {
const browser = await webdriver(next.url, '/')
const browser = await webdriver(next.url, '/404')
await check(
() => browser.eval('next.router.isReady ? "yes" : "no"'),
'yes'
)
await browser.eval('window.beforeNav = 1')
await browser.eval('window.next.router.push("/rewrite-to-dynamic")')
await browser.waitForElementByCss('#blog')
Expand All @@ -193,7 +202,11 @@ describe('Middleware Runtime trailing slash', () => {
})

it('should have correct route params for chained rewrite from middleware to config rewrite', async () => {
const browser = await webdriver(next.url, '/')
const browser = await webdriver(next.url, '/404')
await check(
() => browser.eval('next.router.isReady ? "yes" : "no"'),
'yes'
)
await browser.eval('window.beforeNav = 1')
await browser.eval(
'window.next.router.push("/rewrite-to-config-rewrite")'
Expand All @@ -219,7 +232,11 @@ describe('Middleware Runtime trailing slash', () => {
})

it('should have correct route params for rewrite from config dynamic route', async () => {
const browser = await webdriver(next.url, '/')
const browser = await webdriver(next.url, '/404')
await check(
() => browser.eval('next.router.isReady ? "yes" : "no"'),
'yes'
)
await browser.eval('window.beforeNav = 1')
await browser.eval('window.next.router.push("/rewrite-3")')
await browser.waitForElementByCss('#blog')
Expand Down