Skip to content

Commit

Permalink
fix(next/image): prefer sharp defaults, use mozjpeg for JPEG
Browse files Browse the repository at this point in the history
- Default concurrency detection is better than os.cpus() as it
inspects things like CPU set/affinity as well as the memory
allocator.

- Prevents use of archaic chroma subsampling for AVIF output.
This should improve the quality of red/orange edges and slightly
reduce file size as AV1 chroma-from-luma prediction works better.

- There's no need to set sequentialRead as sharp now manages this
for each input image and the operations in the pipeline.

- Defaults JPEG output to use mozjpeg features such as trellis
quantisation as these produce smaller file sizes. This should
produce consistent output with squoosh at the same quality.
  • Loading branch information
lovell committed May 16, 2024
1 parent 5469e64 commit 670fea4
Showing 1 changed file with 2 additions and 13 deletions.
15 changes: 2 additions & 13 deletions packages/next/src/server/image-optimizer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createHash } from 'crypto'
import { promises } from 'fs'
import { cpus } from 'os'
import type { IncomingMessage, ServerResponse } from 'http'
import { mediaType } from 'next/dist/compiled/@hapi/accept'
import contentDisposition from 'next/dist/compiled/content-disposition'
Expand Down Expand Up @@ -47,13 +46,6 @@ function getSharp() {
}
try {
_sharp = require('sharp')
if (_sharp && _sharp.concurrency() > 1) {
// Reducing concurrency should reduce the memory usage too.
// We more aggressively reduce in dev but also reduce in prod.
// https://sharp.pixelplumbing.com/api-utility#concurrency
const divisor = process.env.NODE_ENV === 'development' ? 4 : 2
_sharp.concurrency(Math.floor(Math.max(cpus().length / divisor, 1)))
}
} catch (e: unknown) {
if (isError(e) && e.code === 'MODULE_NOT_FOUND') {
throw new Error(
Expand Down Expand Up @@ -444,9 +436,7 @@ export async function optimizeImage({
nextConfigOutput?: 'standalone' | 'export'
}): Promise<Buffer> {
const sharp = getSharp()
const transformer = sharp(buffer, { sequentialRead: true })
.timeout({ seconds: 7 })
.rotate()
const transformer = sharp(buffer).timeout({ seconds: 7 }).rotate()

if (height) {
transformer.resize(width, height)
Expand All @@ -460,14 +450,13 @@ export async function optimizeImage({
const avifQuality = quality - 15
transformer.avif({
quality: Math.max(avifQuality, 0),
chromaSubsampling: '4:2:0', // same as webp
})
} else if (contentType === WEBP) {
transformer.webp({ quality })
} else if (contentType === PNG) {
transformer.png({ quality })
} else if (contentType === JPEG) {
transformer.jpeg({ quality, progressive: true })
transformer.jpeg({ quality, mozjpeg: true })
}

const optimizedBuffer = await transformer.toBuffer()
Expand Down

0 comments on commit 670fea4

Please sign in to comment.