diff --git a/cypress/e2e/middleware/enhanced.cy.ts b/cypress/e2e/middleware/enhanced.cy.ts
index 2d282a18bc..60445f3140 100644
--- a/cypress/e2e/middleware/enhanced.cy.ts
+++ b/cypress/e2e/middleware/enhanced.cy.ts
@@ -1,5 +1,19 @@
describe('Enhanced middleware', () => {
- it('rewrites the response body', () => {
+ it('rewrites the response body using request.rewrite()', () => {
+ cy.visit('/request-rewrite')
+ cy.get('#message').contains('This was static (& escaping test &) but has been transformed in')
+ cy.contains("This is an ad that isn't shown by default")
+ })
+
+ it('modifies the page props when using request.rewrite()', () => {
+ cy.visit('/request-rewrite')
+ cy.get('script#__NEXT_DATA__').then((element) => {
+ const { props } = JSON.parse(element.text());
+ expect(props.pageProps.message).to.include('This was static (& escaping test &) but has been transformed in')
+ })
+ })
+
+ it('rewrites the response body using request.next()', () => {
cy.visit('/static')
cy.get('#message').contains('This was static (& escaping test &) but has been transformed in')
cy.contains("This is an ad that isn't shown by default")
diff --git a/demos/middleware/middleware.ts b/demos/middleware/middleware.ts
index 5950c98671..d93e5fa9b6 100644
--- a/demos/middleware/middleware.ts
+++ b/demos/middleware/middleware.ts
@@ -27,7 +27,22 @@ export async function middleware(req: NextRequest) {
const message = `This was static (& escaping test &) but has been transformed in ${req.geo?.city}`
// Transform the response HTML and props
- res.replaceText('p[id=message]', message)
+ res.replaceText('#message', message)
+ res.setPageProp('message', message)
+ res.setPageProp('showAd', true)
+
+ res.headers.set('x-modified-edge', 'true')
+ res.headers.set('x-is-deno', 'Deno' in globalThis ? 'true' : 'false')
+ return res
+ }
+
+ if (pathname.startsWith('/request-rewrite')) {
+ // request.rewrite() should return the MiddlewareResponse object instead of the Response object.
+ const res = await request.rewrite('/static-rewrite')
+ const message = `This was static (& escaping test &) but has been transformed in ${req.geo?.city}`
+
+ // Transform the response HTML and props
+ res.replaceText('#message', message)
res.setPageProp('message', message)
res.setPageProp('showAd', true)
@@ -138,6 +153,7 @@ export const config = {
'/headers',
'/cookies/:path*',
{ source: '/static' },
+ {source: '/request-rewrite' },
{ source: '/matcher-cookie'},
{ source: '/shows/((?!99|88).*)' },
{
diff --git a/demos/middleware/pages/request-rewrite.js b/demos/middleware/pages/request-rewrite.js
new file mode 100644
index 0000000000..84bfcd628c
--- /dev/null
+++ b/demos/middleware/pages/request-rewrite.js
@@ -0,0 +1,10 @@
+const Rewrite = () => {
+ return (
+
+
This should have been rewritten
+
+ )
+ }
+
+export default Rewrite
+
diff --git a/demos/middleware/pages/static-rewrite.js b/demos/middleware/pages/static-rewrite.js
new file mode 100644
index 0000000000..bbcf45f181
--- /dev/null
+++ b/demos/middleware/pages/static-rewrite.js
@@ -0,0 +1,37 @@
+import * as React from 'react'
+
+const useHydrated = () => {
+ const [hydrated, setHydrated] = React.useState(false)
+ React.useEffect(() => {
+ setHydrated(true)
+ }, [])
+ return hydrated
+}
+
+const Page = ({ message, showAd }) => {
+ const hydrated = useHydrated()
+ return (
+
+
{message}
+ {hydrated && showAd ? (
+
+
This is an ad that isn't shown by default on static test 2 page
+

+
+ ) : (
+
No ads for me
+ )}
+
+ )
+}
+
+export async function getStaticProps() {
+ return {
+ props: {
+ message: 'This is a static page',
+ showAd: false,
+ },
+ }
+}
+
+export default Page
diff --git a/packages/next/src/middleware/request.ts b/packages/next/src/middleware/request.ts
index 02bed8a0b4..335642cb5d 100644
--- a/packages/next/src/middleware/request.ts
+++ b/packages/next/src/middleware/request.ts
@@ -1,6 +1,5 @@
import type { Context } from '@netlify/edge-functions'
import type { NextURL } from 'next/dist/server/web/next-url'
-import { NextResponse } from 'next/server'
import type { NextRequest as InternalNextRequest } from 'next/server'
import { MiddlewareResponse } from './response'
@@ -64,16 +63,17 @@ export class MiddlewareRequest extends Request {
if (response.status === 301 && locationHeader?.startsWith('/')) {
response = await this.context.rewrite(locationHeader)
}
-
return new MiddlewareResponse(response)
}
- rewrite(destination: string | URL | NextURL, init?: ResponseInit): NextResponse {
+ async rewrite(destination: string | URL | NextURL, init?: ResponseInit) {
if (typeof destination === 'string' && destination.startsWith('/')) {
destination = new URL(destination, this.url)
}
this.applyHeaders()
- return NextResponse.rewrite(destination, init)
+ const response = await this.context.rewrite(destination)
+
+ return new MiddlewareResponse(response, init)
}
get headers() {
diff --git a/packages/next/src/middleware/response.ts b/packages/next/src/middleware/response.ts
index 2e3eb519b9..90b9a9244a 100644
--- a/packages/next/src/middleware/response.ts
+++ b/packages/next/src/middleware/response.ts
@@ -10,7 +10,9 @@ export type NextDataTransform = }>(
export class MiddlewareResponse extends NextResponse {
private readonly dataTransforms: NextDataTransform[]
private readonly elementHandlers: Array<[selector: string, handlers: ElementHandlers]>
- constructor(public originResponse: Response) {
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ constructor(public originResponse: Response, init?: ResponseInit) {
// we need to propagate the set-cookie header, so response.cookies.get works correctly
const initHeaders = new Headers()
if (originResponse.headers.has('set-cookie')) {
diff --git a/test/e2e/next-test-lib/next-test-utils.js b/test/e2e/next-test-lib/next-test-utils.js
index f8632fd020..fb0b6328f3 100644
--- a/test/e2e/next-test-lib/next-test-utils.js
+++ b/test/e2e/next-test-lib/next-test-utils.js
@@ -2,7 +2,7 @@
import spawn from 'cross-spawn'
import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'fs'
import { writeFile } from 'fs-extra'
-import { fetch as undiciFetch } from 'undici'
+import { fetch as undiciFetch } from 'next/dist/compiled/undici'
import nodeFetch from 'node-fetch'
import path from 'path'
import qs from 'querystring'
@@ -111,7 +111,7 @@ async function processChunkedResponse(response) {
* @param {string | number} appPort
* @param {string} pathname
* @param {Record | string | undefined} [query]
- * @param {import("undici").RequestInit} [opts]
+ * @param {import("next/dist/compiled/undici").RequestInit} [opts]
* @returns {Promise}
*/
export function renderViaHTTP(appPort, pathname, query, opts) {
@@ -121,11 +121,11 @@ export function renderViaHTTP(appPort, pathname, query, opts) {
/**
* @param {string | number} appPort
* @param {string} pathname
- * @param {Record | string | undefined} query
- * @param {RequestInit} opts
- * @returns {Promise}
+ * @param {Record | string | null | undefined} [query]
+ * @param {import('node-fetch').RequestInit} [opts]
+ * @returns {Promise}
*/
-export async function fetchViaHTTP(appPort, pathname, query = undefined, opts = undefined, useUndici = false) {
+export function fetchViaHTTP(appPort, pathname, query, opts, useUndici = false) {
const url = `${pathname}${typeof query === 'string' ? query : query ? `?${qs.stringify(query)}` : ''}`
const fetch = useUndici ? undiciFetch : nodeFetch
const fullUrl = getFullUrl(appPort, url)