Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ function generateClientSsgManifest(
}

function pageToRoute(page: string) {
const routeRegex = getNamedRouteRegex(page)
const routeRegex = getNamedRouteRegex(page, true)
return {
page,
regex: normalizeRouteRegex(routeRegex.re.source),
Expand Down Expand Up @@ -2116,7 +2116,8 @@ export default async function build(

if (isDynamicRoute(page)) {
const routeRegex = getNamedRouteRegex(
dataRoute.replace(/\.json$/, '')
dataRoute.replace(/\.json$/, ''),
true
)

dataRouteRegex = normalizeRouteRegex(
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { ServerRuntime } from '../../types'

export const NEXT_QUERY_PARAM_PREFIX = 'nextPriv'

// in seconds
export const CACHE_ONE_YEAR = 31536000

Expand Down
20 changes: 19 additions & 1 deletion packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ import { RouteKind } from './future/route-kind'
import { handleInternalServerErrorResponse } from './future/route-modules/helpers/response-handlers'
import { parseNextReferrerFromHeaders } from './lib/parse-next-referrer'
import { fromNodeHeaders, toNodeHeaders } from './web/utils'
import { NEXT_QUERY_PARAM_PREFIX } from '../lib/constants'

export type FindComponentsResult = {
components: LoadComponentsReturnType
Expand Down Expand Up @@ -781,6 +782,21 @@ export default abstract class Server<ServerOptions extends Options = Options> {
addRequestMeta(req, '_nextRewroteUrl', parsedUrl.pathname!)
addRequestMeta(req, '_nextDidRewrite', true)
}
const routeParamKeys = new Set<string>()

for (const key of Object.keys(parsedUrl.query)) {
const value = parsedUrl.query[key]

if (key.startsWith(NEXT_QUERY_PARAM_PREFIX)) {
const normalizedKey = key.substring(
NEXT_QUERY_PARAM_PREFIX.length
)
parsedUrl.query[normalizedKey] = value

routeParamKeys.add(normalizedKey)
delete parsedUrl.query[key]
}
}

// interpolate dynamic params and normalize URL if needed
if (pageIsDynamic) {
Expand Down Expand Up @@ -859,7 +875,6 @@ export default abstract class Server<ServerOptions extends Options = Options> {
matchedPath = utils.interpolateDynamicPath(srcPathname, params)
req.url = utils.interpolateDynamicPath(req.url!, params)
}
Object.assign(parsedUrl.query, params)
}

if (pageIsDynamic || didRewrite) {
Expand All @@ -868,6 +883,9 @@ export default abstract class Server<ServerOptions extends Options = Options> {
...Object.keys(utils.defaultRouteRegex?.groups || {}),
])
}
for (const key of routeParamKeys) {
delete parsedUrl.query[key]
}
parsedUrl.pathname = `${this.nextConfig.basePath || ''}${
matchedPath === '/' && this.nextConfig.basePath ? '' : matchedPath
}`
Expand Down
10 changes: 8 additions & 2 deletions packages/next/src/server/server-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { TEMPORARY_REDIRECT_STATUS } from '../shared/lib/constants'
import { addRequestMeta } from './request-meta'
import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-slash'
import { normalizeRscPath } from '../shared/lib/router/utils/app-paths'
import { NEXT_QUERY_PARAM_PREFIX } from '../lib/constants'

export function normalizeVercelUrl(
req: BaseNextRequest | IncomingMessage,
Expand All @@ -37,8 +38,13 @@ export function normalizeVercelUrl(
const _parsedUrl = parseUrl(req.url!, true)
delete (_parsedUrl as any).search

for (const param of paramKeys || Object.keys(defaultRouteRegex.groups)) {
delete _parsedUrl.query[param]
for (const key of Object.keys(_parsedUrl.query)) {
if (
key.startsWith(NEXT_QUERY_PARAM_PREFIX) ||
(paramKeys || Object.keys(defaultRouteRegex.groups)).includes(key)
) {
delete _parsedUrl.query[key]
}
}
req.url = formatUrl(_parsedUrl)
}
Expand Down
19 changes: 15 additions & 4 deletions packages/next/src/shared/lib/router/utils/route-regex.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { escapeStringRegexp } from '../../escape-regexp'
import { removeTrailingSlash } from './remove-trailing-slash'

const NEXT_QUERY_PARAM_PREFIX = 'nextPriv'

export interface Group {
pos: number
repeat: boolean
Expand Down Expand Up @@ -88,7 +90,7 @@ function buildGetSafeRouteKey() {
}
}

function getNamedParametrizedRoute(route: string) {
function getNamedParametrizedRoute(route: string, prefixRouteKeys?: boolean) {
const segments = removeTrailingSlash(route).slice(1).split('/')
const getSafeRouteKey = buildGetSafeRouteKey()
const routeKeys: { [named: string]: string } = {}
Expand All @@ -115,7 +117,13 @@ function getNamedParametrizedRoute(route: string) {
cleanedKey = getSafeRouteKey()
}

routeKeys[cleanedKey] = key
if (prefixRouteKeys) {
cleanedKey = `${NEXT_QUERY_PARAM_PREFIX}${cleanedKey}`
routeKeys[cleanedKey] = `${NEXT_QUERY_PARAM_PREFIX}${key}`
} else {
routeKeys[cleanedKey] = `${key}`
}

return repeat
? optional
? `(?:/(?<${cleanedKey}>.+?))?`
Expand All @@ -135,8 +143,11 @@ function getNamedParametrizedRoute(route: string) {
* each group is named along with a routeKeys object that indexes the assigned
* named group with its corresponding key.
*/
export function getNamedRouteRegex(normalizedRoute: string) {
const result = getNamedParametrizedRoute(normalizedRoute)
export function getNamedRouteRegex(
normalizedRoute: string,
prefixRouteKey?: boolean
) {
const result = getNamedParametrizedRoute(normalizedRoute, prefixRouteKey)
return {
...getRouteRegex(normalizedRoute),
namedRegex: `^${result.namedParameterizedRoute}(?:/)?$`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use client'

import { useSearchParams } from 'next/navigation'

export default function IdPage({ children, params }) {
return (
<>
Expand All @@ -8,6 +10,10 @@ export default function IdPage({ children, params }) {
<span id="id-page-params">{JSON.stringify(params)}</span>
</p>
{children}

<p id="search-params">
{JSON.stringify(Object.fromEntries(useSearchParams()))}
</p>
</>
)
}
4 changes: 3 additions & 1 deletion test/e2e/app-dir/app/app/dynamic/[category]/[id]/page.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
export default function IdPage({ children, params }) {
export default function IdPage({ children, params, searchParams }) {
return (
<>
<p>
Id Page. Params:{' '}
<span id="id-page-params">{JSON.stringify(params)}</span>
</p>
{children}

<p id="search-params">{JSON.stringify(searchParams)}</p>
</>
)
}
29 changes: 29 additions & 0 deletions test/e2e/app-dir/app/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,35 @@ createNextDescribe(
files: __dirname,
},
({ next, isNextDev: isDev, isNextStart, isNextDeploy }) => {
it('should have correct searchParams and params (server)', async () => {
const html = await next.render('/dynamic/category-1/id-2?query1=value2')
const $ = cheerio.load(html)

expect(JSON.parse($('#id-page-params').text())).toEqual({
category: 'category-1',
id: 'id-2',
})
expect(JSON.parse($('#search-params').text())).toEqual({
query1: 'value2',
})
})

it('should have correct searchParams and params (client)', async () => {
const browser = await next.browser(
'/dynamic-client/category-1/id-2?query1=value2'
)
const html = await browser.eval('document.documentElement.innerHTML')
const $ = cheerio.load(html)

expect(JSON.parse($('#id-page-params').text())).toEqual({
category: 'category-1',
id: 'id-2',
})
expect(JSON.parse($('#search-params').text())).toEqual({
query1: 'value2',
})
})

if (!isDev) {
it('should successfully detect app route during prefetch', async () => {
const browser = await next.browser('/')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ createNextDescribe(
'parallel-routes-and-interception',
{
files: __dirname,
// TODO: remove after deployment handling is updated
skipDeployment: true,
},
({ next }) => {
describe('parallel routes', () => {
Expand Down
24 changes: 0 additions & 24 deletions test/e2e/app-dir/parallel-routes-and-interception/tsconfig.json

This file was deleted.

4 changes: 2 additions & 2 deletions test/e2e/edge-render-getserversideprops/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,10 @@ describe('edge-render-getserversideprops', () => {
),
namedDataRouteRegex: `^/_next/data/${escapeStringRegexp(
next.buildId
)}/(?<id>[^/]+?)\\.json$`,
)}/(?<nextPrivid>[^/]+?)\\.json$`,
page: '/[id]',
routeKeys: {
id: 'id',
nextPrivid: 'nextPrivid',
},
},
])
Expand Down
22 changes: 11 additions & 11 deletions test/e2e/getserversideprops/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,40 +43,40 @@ const expectedManifestRoutes = () => [
{
namedDataRouteRegex: `^/_next/data/${escapeRegex(
buildId
)}/blog/(?<post>[^/]+?)\\.json$`,
)}/blog/(?<nextPrivpost>[^/]+?)\\.json$`,
dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(buildId)}\\/blog\\/([^\\/]+?)\\.json$`
),
page: '/blog/[post]',
routeKeys: {
post: 'post',
nextPrivpost: 'nextPrivpost',
},
},
{
namedDataRouteRegex: `^/_next/data/${escapeRegex(
buildId
)}/blog/(?<post>[^/]+?)/(?<comment>[^/]+?)\\.json$`,
)}/blog/(?<nextPrivpost>[^/]+?)/(?<nextPrivcomment>[^/]+?)\\.json$`,
dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(
buildId
)}\\/blog\\/([^\\/]+?)\\/([^\\/]+?)\\.json$`
),
page: '/blog/[post]/[comment]',
routeKeys: {
post: 'post',
comment: 'comment',
nextPrivpost: 'nextPrivpost',
nextPrivcomment: 'nextPrivcomment',
},
},
{
namedDataRouteRegex: `^/_next/data/${escapeRegex(
buildId
)}/catchall/(?<path>.+?)\\.json$`,
)}/catchall/(?<nextPrivpath>.+?)\\.json$`,
dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(buildId)}\\/catchall\\/(.+?)\\.json$`
),
page: '/catchall/[...path]',
routeKeys: {
path: 'path',
nextPrivpath: 'nextPrivpath',
},
},
{
Expand Down Expand Up @@ -127,10 +127,10 @@ const expectedManifestRoutes = () => [
)}\\/not\\-found\\/([^\\/]+?)\\.json$`,
namedDataRouteRegex: `^/_next/data/${escapeRegex(
buildId
)}/not\\-found/(?<slug>[^/]+?)\\.json$`,
)}/not\\-found/(?<nextPrivslug>[^/]+?)\\.json$`,
page: '/not-found/[slug]',
routeKeys: {
slug: 'slug',
nextPrivslug: 'nextPrivslug',
},
},
{
Expand Down Expand Up @@ -182,15 +182,15 @@ const expectedManifestRoutes = () => [
{
namedDataRouteRegex: `^/_next/data/${escapeRegex(
buildId
)}/user/(?<user>[^/]+?)/profile\\.json$`,
)}/user/(?<nextPrivuser>[^/]+?)/profile\\.json$`,
dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(
buildId
)}\\/user\\/([^\\/]+?)\\/profile\\.json$`
),
page: '/user/[user]/profile',
routeKeys: {
user: 'user',
nextPrivuser: 'nextPrivuser',
},
},
]
Expand Down
Loading