Skip to content

Commit

Permalink
refactor(middleware): deprecate request.page
Browse files Browse the repository at this point in the history
  • Loading branch information
Kikobeats committed Jun 1, 2022
1 parent 1420dd9 commit 092e6f0
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 33 deletions.
61 changes: 61 additions & 0 deletions errors/middleware-request-page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Deprecated page into Middleware API

#### Why This Error Occurred

Your application is interacting with `request.page`, and it's being deprecated.

```typescript
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'

export function middleware(request: NextRequest) {
const { params } = event.request.page
const { locale, slug } = params

if (locale && slug) {
const { search, protocol, host } = request.nextUrl
const url = new URL(`${protocol}//${locale}.${host}/${slug}${search}`)
return NextResponse.redirect(url)
}
}
```

#### Possible Ways to Fix It

You can use [URLPattern](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) instead to have the same behavior:

```typescript
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'

const PATTERNS = [
[
new URLPattern({ pathname: '/:locale/:slug' }),
({ pathname }) => pathname.groups,
],
]

const params = (url) => {
const input = url.split('?')[0]
let result = {}

for (const [pattern, handler] of PATTERNS) {
const patternResult = pattern.exec(input)
if (patternResult !== null && 'pathname' in patternResult) {
result = handler(patternResult)
break
}
}
return result
}

export function middleware(request: NextRequest) {
const { locale, slug } = params(request.url)

if (locale && slug) {
const { search, protocol, host } = request.nextUrl
const url = new URL(`${protocol}//${locale}.${host}/${slug}${search}`)
return NextResponse.redirect(url)
}
}
```
9 changes: 4 additions & 5 deletions packages/next/server/web/adapter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { NextMiddleware, RequestData, FetchEventResult } from './types'
import type { RequestInit } from './spec-extension/request'
import { DeprecationError } from './error'
import { DeprecationSignatureError } from './error'
import { fromNodeHeaders } from './utils'
import { NextFetchEvent } from './spec-extension/fetch-event'
import { NextRequest } from './spec-extension/request'
Expand All @@ -23,7 +23,6 @@ export async function adapter(params: {
ip: params.request.ip,
method: params.request.method,
nextConfig: params.request.nextConfig,
page: params.request.page,
},
})

Expand Down Expand Up @@ -97,14 +96,14 @@ class NextRequestHint extends NextRequest {
}

get request() {
throw new DeprecationError({ page: this.sourcePage })
throw new DeprecationSignatureError({ page: this.sourcePage })
}

respondWith() {
throw new DeprecationError({ page: this.sourcePage })
throw new DeprecationSignatureError({ page: this.sourcePage })
}

waitUntil() {
throw new DeprecationError({ page: this.sourcePage })
throw new DeprecationSignatureError({ page: this.sourcePage })
}
}
10 changes: 9 additions & 1 deletion packages/next/server/web/error.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export class DeprecationError extends Error {
export class DeprecationSignatureError extends Error {
constructor({ page }: { page: string }) {
super(`The middleware "${page}" accepts an async API directly with the form:
Expand All @@ -10,3 +10,11 @@ export class DeprecationError extends Error {
`)
}
}

export class DeprecationPageError extends Error {
constructor() {
super(`The request.page has been deprecated in favour of URLPattern.
Read more: https://nextjs.org/docs/messages/middleware-request-page
`)
}
}
6 changes: 3 additions & 3 deletions packages/next/server/web/spec-extension/fetch-event.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DeprecationError } from '../error'
import { DeprecationSignatureError } from '../error'
import { NextRequest } from './request'

const responseSymbol = Symbol('response')
Expand Down Expand Up @@ -42,7 +42,7 @@ export class NextFetchEvent extends FetchEvent {
* Read more: https://nextjs.org/docs/messages/middleware-new-signature
*/
get request() {
throw new DeprecationError({
throw new DeprecationSignatureError({
page: this.sourcePage,
})
}
Expand All @@ -53,7 +53,7 @@ export class NextFetchEvent extends FetchEvent {
* Read more: https://nextjs.org/docs/messages/middleware-new-signature
*/
respondWith() {
throw new DeprecationError({
throw new DeprecationSignatureError({
page: this.sourcePage,
})
}
Expand Down
12 changes: 2 additions & 10 deletions packages/next/server/web/spec-extension/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NextURL } from '../next-url'
import { isBot } from '../../utils'
import { toNodeHeaders, validateURL } from '../utils'
import parseua from 'next/dist/compiled/ua-parser-js'
import { DeprecationPageError } from '../error'

import { NextCookies } from './cookies'

Expand All @@ -14,7 +15,6 @@ export class NextRequest extends Request {
cookies: NextCookies
geo: RequestData['geo']
ip?: string
page?: { name?: string; params?: { [key: string]: string | string[] } }
ua?: UserAgent | null
url: NextURL
}
Expand All @@ -27,7 +27,6 @@ export class NextRequest extends Request {
cookies: new NextCookies(this),
geo: init.geo || {},
ip: init.ip,
page: init.page,
url: new NextURL(url, {
headers: toNodeHeaders(this.headers),
nextConfig: init.nextConfig,
Expand Down Expand Up @@ -56,10 +55,7 @@ export class NextRequest extends Request {
}

public get page() {
return {
name: this[INTERNALS].page?.name,
params: this[INTERNALS].page?.params,
}
throw new DeprecationPageError()
}

public get ua() {
Expand Down Expand Up @@ -98,10 +94,6 @@ export interface RequestInit extends globalThis.RequestInit {
i18n?: I18NConfig | null
trailingSlash?: boolean
}
page?: {
name?: string
params?: { [key: string]: string | string[] }
}
}

interface UserAgent {
Expand Down
37 changes: 34 additions & 3 deletions test/integration/middleware-general/middleware.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,38 @@
/* global globalThis */
/* global globalThis, URLPattern */
import { NextRequest, NextResponse } from 'next/server'
import magicValue from 'shared-package'

const PATTERNS = [
[
new URLPattern({ pathname: '/:locale/:id' }),
({ pathname }) => ({
pathname: '/:locale/:id',
params: pathname.groups,
}),
],
[
new URLPattern({ pathname: '/:id' }),
({ pathname }) => ({
pathname: '/:id',
params: pathname.groups,
}),
],
]

const params = (url) => {
const input = url.split('?')[0]
let result = {}

for (const [pattern, handler] of PATTERNS) {
const patternResult = pattern.exec(input)
if (patternResult !== null && 'pathname' in patternResult) {
result = handler(patternResult)
break
}
}
return result
}

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

Expand Down Expand Up @@ -151,8 +182,8 @@ export async function middleware(request) {
headers: {
'req-url-basepath': request.nextUrl.basePath,
'req-url-pathname': request.nextUrl.pathname,
'req-url-params': JSON.stringify(request.page.params),
'req-url-page': request.page.name,
'req-url-params':
url.pathname !== '/static' ? JSON.stringify(params(request.url)) : '{}',
'req-url-query': request.nextUrl.searchParams.get('foo'),
'req-url-locale': request.nextUrl.locale,
},
Expand Down
32 changes: 23 additions & 9 deletions test/integration/middleware-general/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,14 @@ function tests(context, locale = '') {

it(`should validate & parse request url from any route`, async () => {
const res = await fetchViaHTTP(context.appPort, `${locale}/static`)

expect(res.headers.get('req-url-basepath')).toBe('')
expect(res.headers.get('req-url-pathname')).toBe('/static')
expect(res.headers.get('req-url-params')).not.toBe('{}')

const { pathname, params } = JSON.parse(res.headers.get('req-url-params'))
expect(pathname).toBe(undefined)
expect(params).toEqual(undefined)

expect(res.headers.get('req-url-query')).not.toBe('bar')
if (locale !== '') {
expect(res.headers.get('req-url-locale')).toBe(locale.slice(1))
Expand All @@ -216,30 +221,39 @@ function tests(context, locale = '') {

it(`should validate & parse request url from a dynamic route with params`, async () => {
const res = await fetchViaHTTP(context.appPort, `/fr/1`)

expect(res.headers.get('req-url-basepath')).toBe('')
expect(res.headers.get('req-url-pathname')).toBe('/1')
expect(res.headers.get('req-url-params')).toBe('{"id":"1"}')
expect(res.headers.get('req-url-page')).toBe('/[id]')

const { pathname, params } = JSON.parse(res.headers.get('req-url-params'))
expect(pathname).toBe('/:locale/:id')
expect(params).toEqual({ locale: 'fr', id: '1' })

expect(res.headers.get('req-url-query')).not.toBe('bar')
expect(res.headers.get('req-url-locale')).toBe('fr')
})

it(`should validate & parse request url from a dynamic route with params and no query`, async () => {
const res = await fetchViaHTTP(context.appPort, `/fr/abc123`)
expect(res.headers.get('req-url-basepath')).toBe('')
expect(res.headers.get('req-url-pathname')).toBe('/abc123')
expect(res.headers.get('req-url-params')).toBe('{"id":"abc123"}')
expect(res.headers.get('req-url-page')).toBe('/[id]')

const { pathname, params } = JSON.parse(res.headers.get('req-url-params'))
expect(pathname).toBe('/:locale/:id')
expect(params).toEqual({ locale: 'fr', id: 'abc123' })

expect(res.headers.get('req-url-query')).not.toBe('bar')
expect(res.headers.get('req-url-locale')).toBe('fr')
})

it(`should validate & parse request url from a dynamic route with params and query`, async () => {
const res = await fetchViaHTTP(context.appPort, `/abc123?foo=bar`)
expect(res.headers.get('req-url-basepath')).toBe('')
expect(res.headers.get('req-url-pathname')).toBe('/abc123')
expect(res.headers.get('req-url-params')).toBe('{"id":"abc123"}')
expect(res.headers.get('req-url-page')).toBe('/[id]')

const { pathname, params } = JSON.parse(res.headers.get('req-url-params'))

expect(pathname).toBe('/:id')
expect(params).toEqual({ id: 'abc123' })

expect(res.headers.get('req-url-query')).toBe('bar')
expect(res.headers.get('req-url-locale')).toBe('en')
})
Expand Down
2 changes: 0 additions & 2 deletions test/production/middleware-typescript/app/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ export const middleware: NextMiddleware = function (request) {
data: 'hello from middleware',
'req-url-basepath': request.nextUrl.basePath,
'req-url-pathname': request.nextUrl.pathname,
'req-url-params': JSON.stringify(request.page.params) || '',
'req-url-page': request.page.name || '',
'req-url-query': request.nextUrl.searchParams.get('foo') || '',
'req-url-locale': request.nextUrl.locale,
},
Expand Down

0 comments on commit 092e6f0

Please sign in to comment.