From 93fe3feb2371a57f23d71822617afdeec2b6b26f Mon Sep 17 00:00:00 2001 From: Kiko Beats Date: Tue, 31 May 2022 21:39:19 +0200 Subject: [PATCH] refactor(middleware): deprecate request.page --- errors/manifest.json | 4 ++ errors/middleware-request-page.md | 61 +++++++++++++++++++ packages/next/server/web/adapter.ts | 9 ++- packages/next/server/web/error.ts | 10 ++- .../server/web/spec-extension/fetch-event.ts | 6 +- .../next/server/web/spec-extension/request.ts | 12 +--- .../middleware-general/middleware.js | 37 ++++++++++- .../middleware-general/test/index.test.js | 32 +++++++--- .../middleware-typescript/app/middleware.ts | 2 - 9 files changed, 140 insertions(+), 33 deletions(-) create mode 100644 errors/middleware-request-page.md diff --git a/errors/manifest.json b/errors/manifest.json index 79adcf8104af1..c2273887f9593 100644 --- a/errors/manifest.json +++ b/errors/manifest.json @@ -669,6 +669,10 @@ { "title": "returning-response-body-in-middleware", "path": "/errors/returning-response-body-in-middleware.md" + }, + { + "title": "middleware-request-page.md", + "path": "/errors/middleware-request-page.md" } ] } diff --git a/errors/middleware-request-page.md b/errors/middleware-request-page.md new file mode 100644 index 0000000000000..12b92e2062dde --- /dev/null +++ b/errors/middleware-request-page.md @@ -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) + } +} +``` diff --git a/packages/next/server/web/adapter.ts b/packages/next/server/web/adapter.ts index 8c4853edade5f..f66af7152ec56 100644 --- a/packages/next/server/web/adapter.ts +++ b/packages/next/server/web/adapter.ts @@ -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' @@ -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, }, }) @@ -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 }) } } diff --git a/packages/next/server/web/error.ts b/packages/next/server/web/error.ts index cb55b3e5dd0c7..f94f006195e5c 100644 --- a/packages/next/server/web/error.ts +++ b/packages/next/server/web/error.ts @@ -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: @@ -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 + `) + } +} diff --git a/packages/next/server/web/spec-extension/fetch-event.ts b/packages/next/server/web/spec-extension/fetch-event.ts index 249f36f0d9530..a3bbc5ed50dee 100644 --- a/packages/next/server/web/spec-extension/fetch-event.ts +++ b/packages/next/server/web/spec-extension/fetch-event.ts @@ -1,4 +1,4 @@ -import { DeprecationError } from '../error' +import { DeprecationSignatureError } from '../error' import { NextRequest } from './request' const responseSymbol = Symbol('response') @@ -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, }) } @@ -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, }) } diff --git a/packages/next/server/web/spec-extension/request.ts b/packages/next/server/web/spec-extension/request.ts index 12efda926770c..76675cf2b2e1d 100644 --- a/packages/next/server/web/spec-extension/request.ts +++ b/packages/next/server/web/spec-extension/request.ts @@ -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' @@ -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 } @@ -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, @@ -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() { @@ -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 { diff --git a/test/integration/middleware-general/middleware.js b/test/integration/middleware-general/middleware.js index ee6ed111e6ceb..5e1524473962f 100644 --- a/test/integration/middleware-general/middleware.js +++ b/test/integration/middleware-general/middleware.js @@ -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 @@ -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, }, diff --git a/test/integration/middleware-general/test/index.test.js b/test/integration/middleware-general/test/index.test.js index 43817508e00b7..501850cfabbc1 100644 --- a/test/integration/middleware-general/test/index.test.js +++ b/test/integration/middleware-general/test/index.test.js @@ -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)) @@ -216,10 +221,14 @@ 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') }) @@ -227,9 +236,11 @@ function tests(context, locale = '') { 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') }) @@ -237,9 +248,12 @@ function tests(context, locale = '') { 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') }) diff --git a/test/production/middleware-typescript/app/middleware.ts b/test/production/middleware-typescript/app/middleware.ts index 7bddb2d1537e5..44d2ec9639e7b 100644 --- a/test/production/middleware-typescript/app/middleware.ts +++ b/test/production/middleware-typescript/app/middleware.ts @@ -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, },