diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 4905a0f1125a..4fa36b777d21 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -374,7 +374,7 @@ module.exports = { The default [Image Optimization API](#loader-configuration) will automatically detect the browser's supported image formats via the request's `Accept` header. -If the `Accept` head matches more than one of the configured formats, the first match in the array is used. Therefore, the array order matters. If there is no match, the Image Optimization API will fallback to the original image's format. +If the `Accept` head matches more than one of the configured formats, the first match in the array is used. Therefore, the array order matters. If there is no match (or the source image is [animated](#animated-images)), the Image Optimization API will fallback to the original image's format. If no configuration is provided, the default below is used. @@ -408,7 +408,7 @@ The expiration (or rather Max Age) is defined by either the [`minimumCacheTTL`]( - You can configure [`minimumCacheTTL`](#minimum-cache-ttl) to increase the cache duration when the upstream image does not include `Cache-Control` header or the value is very low. - You can configure [`deviceSizes`](#device-sizes) and [`imageSizes`](#device-sizes) to reduce the total number of possible generated images. -- You can configure [formats](/docs/basic-features/image-optimization.md#acceptable-formats) to disable multiple formats in favor of a single image format. +- You can configure [formats](#acceptable-formats) to disable multiple formats in favor of a single image format. ### Minimum Cache TTL @@ -455,6 +455,12 @@ module.exports = { } ``` +### Animated Images + +The default [loader](#loader) will automatically bypass Image Optimization for animated images and serve the image as-is. + +Auto-detection for animated files is best-effort and supports GIF, APNG, and WebP. If you want to explicitly bypass Image Optimization for a given animated image, use the [unoptimized](#unoptimized) prop. + ## Related For an overview of the Image component features and usage guidelines, see: diff --git a/examples/blog/theme.config.js b/examples/blog/theme.config.js index fb56fe005f6d..993a7b2d7f42 100644 --- a/examples/blog/theme.config.js +++ b/examples/blog/theme.config.js @@ -2,20 +2,19 @@ const YEAR = new Date().getFullYear() export default { footer: ( - - © Your Name. - RSS + ) } diff --git a/packages/next/server/body-streams.ts b/packages/next/server/body-streams.ts index 5ce9a0b3abde..c4fc477d00b4 100644 --- a/packages/next/server/body-streams.ts +++ b/packages/next/server/body-streams.ts @@ -58,14 +58,20 @@ export function clonableBodyForRequest( ) { let bufferedBodyStream: BodyStream | null = null + const endPromise = new Promise((resolve, reject) => { + incomingMessage.on('end', resolve) + incomingMessage.on('error', reject) + }) + return { /** * Replaces the original request body if necessary. * This is done because once we read the body from the original request, * we can't read it again. */ - finalize(): void { + async finalize(): Promise { if (bufferedBodyStream) { + await endPromise replaceRequestBody( incomingMessage, bodyStreamToNodeStream(bufferedBodyStream) diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 65876d9f2fdb..efe6800d06fc 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -1314,7 +1314,7 @@ export default class NextNodeServer extends BaseServer { } } - originalBody?.finalize() + await originalBody?.finalize() return result } diff --git a/packages/next/shared/lib/router/router.ts b/packages/next/shared/lib/router/router.ts index c7c64cf7158a..804f18a0d67c 100644 --- a/packages/next/shared/lib/router/router.ts +++ b/packages/next/shared/lib/router/router.ts @@ -1165,8 +1165,9 @@ export default class Router implements BaseRouter { * request as it is not necessary. */ if ( - (options as any)._h !== 1 || - isDynamicRoute(removePathTrailingSlash(pathname)) + (!options.shallow || (options as any)._h === 1) && + ((options as any)._h !== 1 || + isDynamicRoute(removePathTrailingSlash(pathname))) ) { const effect = await this._preflightRequest({ as, @@ -1864,8 +1865,9 @@ export default class Router implements BaseRouter { locale: string | undefined isPreview: boolean }): Promise { + const asPathname = pathNoQueryHash(options.as) const cleanedAs = delLocale( - hasBasePath(options.as) ? delBasePath(options.as) : options.as, + hasBasePath(asPathname) ? delBasePath(asPathname) : asPathname, options.locale ) diff --git a/test/integration/middleware/core/pages/rewrites/_middleware.js b/test/integration/middleware/core/pages/rewrites/_middleware.js index dcb61e1d176d..2d6b154dc86d 100644 --- a/test/integration/middleware/core/pages/rewrites/_middleware.js +++ b/test/integration/middleware/core/pages/rewrites/_middleware.js @@ -65,7 +65,10 @@ export async function middleware(request) { return NextResponse.rewrite(url) } - if (url.pathname === '/rewrites/rewrite-me-without-hard-navigation') { + if ( + url.pathname === '/rewrites/rewrite-me-without-hard-navigation' || + url.searchParams.get('path') === 'rewrite-me-without-hard-navigation' + ) { url.searchParams.set('middleware', 'foo') url.pathname = request.cookies['about-bypass'] === '1' diff --git a/test/integration/middleware/core/pages/rewrites/index.js b/test/integration/middleware/core/pages/rewrites/index.js index f15b3557ed56..4fbadef290f0 100644 --- a/test/integration/middleware/core/pages/rewrites/index.js +++ b/test/integration/middleware/core/pages/rewrites/index.js @@ -1,6 +1,8 @@ import Link from 'next/link' +import { useRouter } from 'next/router' export default function Home() { + const router = useRouter() return (

Home Page

@@ -32,6 +34,21 @@ export default function Home() { Rewrite me to internal path +
+ { + e.preventDefault() + router.push( + '/rewrites?path=rewrite-me-without-hard-navigation&message=refreshed', + undefined, + { shallow: true } + ) + }} + > + Do not rewrite me +
) } diff --git a/test/integration/middleware/core/test/index.test.js b/test/integration/middleware/core/test/index.test.js index d85a53826ad6..0d53fca1c9ff 100644 --- a/test/integration/middleware/core/test/index.test.js +++ b/test/integration/middleware/core/test/index.test.js @@ -488,6 +488,17 @@ function rewriteTests(log, locale = '') { const element = await browser.elementByCss('.title') expect(await element.text()).toEqual('About Bypassed Page') }) + + it(`${locale} should not call middleware with shallow push`, async () => { + const browser = await webdriver(context.appPort, '/rewrites') + await browser.elementByCss('#link-to-shallow-push').click() + await browser.waitForCondition( + 'new URL(window.location.href).searchParams.get("path") === "rewrite-me-without-hard-navigation"' + ) + await expect(async () => { + await browser.waitForElementByCss('.refreshed', 500) + }).rejects.toThrow() + }) } function redirectTests(locale = '') { diff --git a/test/production/reading-request-body-in-middleware/index.test.ts b/test/production/reading-request-body-in-middleware/index.test.ts index 0f1d61ccfa92..9e64502d8665 100644 --- a/test/production/reading-request-body-in-middleware/index.test.ts +++ b/test/production/reading-request-body-in-middleware/index.test.ts @@ -16,7 +16,11 @@ describe('reading request body in middleware', () => { return new Response('No body', { status: 400 }); } - const json = await request.json(); + let json; + + if (!request.nextUrl.searchParams.has("no_reading")) { + json = await request.json(); + } if (request.nextUrl.searchParams.has("next")) { const res = NextResponse.next(); @@ -141,4 +145,30 @@ describe('reading request body in middleware', () => { }) expect(response.headers.get('x-from-root-middleware')).toEqual('1') }) + + it('passes the body to the api endpoint when no body is consumed on middleware', async () => { + const response = await fetchViaHTTP( + next.url, + '/api/hi', + { + next: '1', + no_reading: '1', + }, + { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify({ + foo: 'bar', + }), + } + ) + expect(response.status).toEqual(200) + expect(await response.json()).toEqual({ + foo: 'bar', + api: true, + }) + expect(response.headers.get('x-from-root-middleware')).toEqual('1') + }) })