mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
feat(next/image): add support for images.qualities in next.config (#74257)
This PR adds support for `images.qualities` configuration which is an allowlist of qualities that can be used with the quality prop on the Image component. - Depends on https://github.com/vercel/vercel/pull/12792
This commit is contained in:
@@ -257,6 +257,10 @@ quality={75} // {number 1-100}
|
||||
|
||||
The quality of the optimized image, an integer between `1` and `100`, where `100` is the best quality and therefore largest file size. Defaults to `75`.
|
||||
|
||||
If the [`qualities`](#qualities) configuration is defined in `next.config.js`, the `quality` prop must match one of the values defined in the configuration.
|
||||
|
||||
> **Good to know**: If the original source image was already low quality, setting the quality prop too high could cause the resulting optimized image to be larger than the original source image.
|
||||
|
||||
### `priority`
|
||||
|
||||
```js
|
||||
@@ -681,6 +685,20 @@ module.exports = {
|
||||
}
|
||||
```
|
||||
|
||||
### `qualities`
|
||||
|
||||
The default [Image Optimization API](#loader) will automatically allow all qualities from 1 to 100. If you wish to restrict the allowed qualities, you can add configuration to `next.config.js`.
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
images: {
|
||||
qualities: [25, 50, 75],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
In this example above, only three qualities are allowed: 25, 50, and 75. If the [`quality`](#quality) prop does not match a value in this array, the image will fail with 400 Bad Request.
|
||||
|
||||
### `formats`
|
||||
|
||||
The default [Image Optimization API](#loader) will automatically detect the browser's supported image formats via the request's `Accept` header in order to determine the best output format.
|
||||
@@ -1076,6 +1094,7 @@ This `next/image` component uses browser native [lazy loading](https://caniuse.c
|
||||
| Version | Changes |
|
||||
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `v15.0.0` | `contentDispositionType` configuration default changed to `attachment`. |
|
||||
| `v14.2.23` | `qualities` configuration added. |
|
||||
| `v14.2.15` | `decoding` prop added and `localPatterns` configuration added. |
|
||||
| `v14.2.14` | `remotePatterns.search` prop added. |
|
||||
| `v14.2.0` | `overrideSrc` prop added. |
|
||||
|
||||
@@ -41,6 +41,8 @@ module.exports = {
|
||||
localPatterns: [],
|
||||
// limit of 50 objects
|
||||
remotePatterns: [],
|
||||
// limit of 20 integers
|
||||
qualities: [25, 50, 75],
|
||||
// when true, every image will be unoptimized
|
||||
unoptimized: false,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: '`next/image` Un-configured qualities'
|
||||
---
|
||||
|
||||
## Why This Error Occurred
|
||||
|
||||
One of your pages that leverages the `next/image` component, passed a `quality` value that isn't defined in the `images.qualities` property in `next.config.js`.
|
||||
|
||||
## Possible Ways to Fix It
|
||||
|
||||
Add an entry to `images.qualities` array in `next.config.js` with the expected value. For example:
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
images: {
|
||||
qualities: [25, 50, 75],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Useful Links
|
||||
|
||||
- [Image Optimization Documentation](/docs/pages/building-your-application/optimizing/images)
|
||||
- [Qualities Config Documentation](/docs/pages/api-reference/components/image#qualities)
|
||||
@@ -620,5 +620,6 @@
|
||||
"619": "Page not found",
|
||||
"620": "A required parameter (%s) was not provided as %s received %s in getStaticPaths for %s",
|
||||
"621": "Required root params (%s) were not provided in generateStaticParams for %s, please provide at least one value for each.",
|
||||
"622": "A required root parameter (%s) was not provided in generateStaticParams for %s, please provide at least one value."
|
||||
"622": "A required root parameter (%s) was not provided in generateStaticParams for %s, please provide at least one value.",
|
||||
"623": "Invalid quality prop (%s) on \\`next/image\\` does not match \\`images.qualities\\` configured in your \\`next.config.js\\`\\nSee more info: https://nextjs.org/docs/messages/next-image-unconfigured-qualities"
|
||||
}
|
||||
|
||||
@@ -109,13 +109,14 @@ function getImageConfig(
|
||||
'process.env.__NEXT_IMAGE_OPTS': {
|
||||
deviceSizes: config.images.deviceSizes,
|
||||
imageSizes: config.images.imageSizes,
|
||||
qualities: config.images.qualities,
|
||||
path: config.images.path,
|
||||
loader: config.images.loader,
|
||||
dangerouslyAllowSVG: config.images.dangerouslyAllowSVG,
|
||||
unoptimized: config?.images?.unoptimized,
|
||||
...(dev
|
||||
? {
|
||||
// pass domains in development to allow validating on the client
|
||||
// additional config in dev to allow validating on the client
|
||||
domains: config.images.domains,
|
||||
remotePatterns: config.images?.remotePatterns,
|
||||
localPatterns: config.images?.localPatterns,
|
||||
|
||||
@@ -371,7 +371,8 @@ export const Image = forwardRef<HTMLImageElement | null, ImageProps>(
|
||||
const c = configEnv || configContext || imageConfigDefault
|
||||
const allSizes = [...c.deviceSizes, ...c.imageSizes].sort((a, b) => a - b)
|
||||
const deviceSizes = c.deviceSizes.sort((a, b) => a - b)
|
||||
return { ...c, allSizes, deviceSizes }
|
||||
const qualities = c.qualities?.sort((a, b) => a - b)
|
||||
return { ...c, allSizes, deviceSizes, qualities }
|
||||
}, [configContext])
|
||||
|
||||
const { onLoad, onLoadingComplete } = props
|
||||
|
||||
@@ -29,7 +29,7 @@ function normalizeSrc(src: string): string {
|
||||
}
|
||||
|
||||
const supportsFloat = typeof ReactDOM.preload === 'function'
|
||||
|
||||
const DEFAULT_Q = 75
|
||||
const configEnv = process.env.__NEXT_IMAGE_OPTS as any as ImageConfigComplete
|
||||
const loadedImageURLs = new Set<string>()
|
||||
const allImgs = new Map<
|
||||
@@ -190,8 +190,22 @@ function defaultLoader({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (quality && config.qualities && !config.qualities.includes(quality)) {
|
||||
throw new Error(
|
||||
`Invalid quality prop (${quality}) on \`next/image\` does not match \`images.qualities\` configured in your \`next.config.js\`\n` +
|
||||
`See more info: https://nextjs.org/docs/messages/next-image-unconfigured-qualities`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const q =
|
||||
quality ||
|
||||
config.qualities?.reduce((prev, cur) =>
|
||||
Math.abs(cur - DEFAULT_Q) < Math.abs(prev - DEFAULT_Q) ? cur : prev
|
||||
) ||
|
||||
DEFAULT_Q
|
||||
|
||||
if (!config.dangerouslyAllowSVG && src.split('?', 1)[0].endsWith('.svg')) {
|
||||
// Special case to make svg serve as-is to avoid proxying
|
||||
// through the built-in Image Optimization API.
|
||||
@@ -200,7 +214,7 @@ function defaultLoader({
|
||||
|
||||
return `${normalizePathTrailingSlash(config.path)}?url=${encodeURIComponent(
|
||||
src
|
||||
)}&w=${width}&q=${quality || 75}`
|
||||
)}&w=${width}&q=${q}`
|
||||
}
|
||||
|
||||
const loaders = new Map<
|
||||
@@ -641,7 +655,8 @@ export default function Image({
|
||||
const c = configEnv || configContext || imageConfigDefault
|
||||
const allSizes = [...c.deviceSizes, ...c.imageSizes].sort((a, b) => a - b)
|
||||
const deviceSizes = c.deviceSizes.sort((a, b) => a - b)
|
||||
return { ...c, allSizes, deviceSizes }
|
||||
const qualities = c.qualities?.sort((a, b) => a - b)
|
||||
return { ...c, allSizes, deviceSizes, qualities }
|
||||
}, [configContext])
|
||||
|
||||
let rest: Partial<ImageProps> = all
|
||||
|
||||
@@ -540,6 +540,11 @@ export const configSchema: zod.ZodType<NextConfig> = z.lazy(() =>
|
||||
loaderFile: z.string().optional(),
|
||||
minimumCacheTTL: z.number().int().gte(0).optional(),
|
||||
path: z.string().optional(),
|
||||
qualities: z
|
||||
.array(z.number().int().gte(1).lte(100))
|
||||
.min(1)
|
||||
.max(20)
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
logging: z
|
||||
|
||||
@@ -219,6 +219,7 @@ export class ImageOptimizerCache {
|
||||
} = imageData
|
||||
const remotePatterns = nextConfig.images?.remotePatterns || []
|
||||
const localPatterns = nextConfig.images?.localPatterns
|
||||
const qualities = nextConfig.images?.qualities
|
||||
const { url, w, q } = query
|
||||
let href: string
|
||||
|
||||
@@ -334,6 +335,18 @@ export class ImageOptimizerCache {
|
||||
}
|
||||
}
|
||||
|
||||
if (qualities) {
|
||||
if (isDev) {
|
||||
qualities.push(BLUR_QUALITY)
|
||||
}
|
||||
|
||||
if (!qualities.includes(quality)) {
|
||||
return {
|
||||
errorMessage: `"q" parameter (quality) of ${q} is not allowed`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mimeType = getSupportedMimeType(formats || [], req.headers['accept'])
|
||||
|
||||
const isStatic = url.startsWith(
|
||||
|
||||
@@ -286,7 +286,8 @@ export function getImgProps(
|
||||
} else {
|
||||
const allSizes = [...c.deviceSizes, ...c.imageSizes].sort((a, b) => a - b)
|
||||
const deviceSizes = c.deviceSizes.sort((a, b) => a - b)
|
||||
config = { ...c, allSizes, deviceSizes }
|
||||
const qualities = c.qualities?.sort((a, b) => a - b)
|
||||
config = { ...c, allSizes, deviceSizes, qualities }
|
||||
}
|
||||
|
||||
if (typeof defaultLoader === 'undefined') {
|
||||
|
||||
@@ -118,6 +118,9 @@ export type ImageConfigComplete = {
|
||||
/** @see [Remote Patterns](https://nextjs.org/docs/api-reference/next/image#localPatterns) */
|
||||
localPatterns: LocalPattern[] | undefined
|
||||
|
||||
/** @see [Qualities](https://nextjs.org/docs/api-reference/next/image#qualities) */
|
||||
qualities: number[] | undefined
|
||||
|
||||
/** @see [Unoptimized](https://nextjs.org/docs/api-reference/next/image#unoptimized) */
|
||||
unoptimized: boolean
|
||||
}
|
||||
@@ -139,5 +142,6 @@ export const imageConfigDefault: ImageConfigComplete = {
|
||||
contentDispositionType: 'attachment',
|
||||
localPatterns: undefined, // default: allow all local images
|
||||
remotePatterns: [], // default: allow no remote images
|
||||
qualities: undefined, // default: allow all qualities
|
||||
unoptimized: false,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { ImageLoaderPropsWithConfig } from './image-config'
|
||||
|
||||
const DEFAULT_Q = 75
|
||||
|
||||
function defaultLoader({
|
||||
config,
|
||||
src,
|
||||
@@ -72,11 +74,23 @@ function defaultLoader({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (quality && config.qualities && !config.qualities.includes(quality)) {
|
||||
throw new Error(
|
||||
`Invalid quality prop (${quality}) on \`next/image\` does not match \`images.qualities\` configured in your \`next.config.js\`\n` +
|
||||
`See more info: https://nextjs.org/docs/messages/next-image-unconfigured-qualities`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return `${config.path}?url=${encodeURIComponent(src)}&w=${width}&q=${
|
||||
quality || 75
|
||||
}${
|
||||
const q =
|
||||
quality ||
|
||||
config.qualities?.reduce((prev, cur) =>
|
||||
Math.abs(cur - DEFAULT_Q) < Math.abs(prev - DEFAULT_Q) ? cur : prev
|
||||
) ||
|
||||
DEFAULT_Q
|
||||
|
||||
return `${config.path}?url=${encodeURIComponent(src)}&w=${width}&q=${q}${
|
||||
src.startsWith('/_next/static/media/') && process.env.NEXT_DEPLOYMENT_ID
|
||||
? `&dpl=${process.env.NEXT_DEPLOYMENT_ID}`
|
||||
: ''
|
||||
|
||||
@@ -24,6 +24,7 @@ type EventCliSessionStarted = {
|
||||
imageDomainsCount: number | null
|
||||
imageRemotePatternsCount: number | null
|
||||
imageLocalPatternsCount: number | null
|
||||
imageQualities: string | null
|
||||
imageSizes: string | null
|
||||
imageLoader: string | null
|
||||
imageFormats: string | null
|
||||
@@ -80,6 +81,7 @@ export function eventCliSession(
|
||||
| 'imageDomainsCount'
|
||||
| 'imageRemotePatternsCount'
|
||||
| 'imageLocalPatternsCount'
|
||||
| 'imageQualities'
|
||||
| 'imageSizes'
|
||||
| 'imageLoader'
|
||||
| 'imageFormats'
|
||||
@@ -126,6 +128,7 @@ export function eventCliSession(
|
||||
? images.localPatterns.length
|
||||
: null,
|
||||
imageSizes: images?.imageSizes ? images.imageSizes.join(',') : null,
|
||||
imageQualities: images?.qualities ? images.qualities.join(',') : null,
|
||||
imageLoader: images?.loader,
|
||||
imageFormats: images?.formats ? images.formats.join(',') : null,
|
||||
nextConfigOutput: nextConfig?.output || null,
|
||||
|
||||
@@ -258,6 +258,84 @@ describe('Image Optimizer', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should error when qualities length exceeds 20', async () => {
|
||||
await nextConfig.replace(
|
||||
'{ /* replaceme */ }',
|
||||
JSON.stringify({
|
||||
images: {
|
||||
qualities: [
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||
20, 21,
|
||||
],
|
||||
},
|
||||
})
|
||||
)
|
||||
let stderr = ''
|
||||
|
||||
app = await launchApp(appDir, await findPort(), {
|
||||
onStderr(msg) {
|
||||
stderr += msg || ''
|
||||
},
|
||||
})
|
||||
await waitFor(1000)
|
||||
await killApp(app).catch(() => {})
|
||||
await nextConfig.restore()
|
||||
|
||||
expect(stderr).toContain(
|
||||
`Array must contain at most 20 element(s) at "images.qualities"`
|
||||
)
|
||||
})
|
||||
|
||||
it('should error when qualities array has a value thats not an integer', async () => {
|
||||
await nextConfig.replace(
|
||||
'{ /* replaceme */ }',
|
||||
JSON.stringify({
|
||||
images: {
|
||||
qualities: [1, 2, 3, 9.9],
|
||||
},
|
||||
})
|
||||
)
|
||||
let stderr = ''
|
||||
|
||||
app = await launchApp(appDir, await findPort(), {
|
||||
onStderr(msg) {
|
||||
stderr += msg || ''
|
||||
},
|
||||
})
|
||||
await waitFor(1000)
|
||||
await killApp(app).catch(() => {})
|
||||
await nextConfig.restore()
|
||||
|
||||
expect(stderr).toContain(
|
||||
`Expected integer, received float at "images.qualities[3]"`
|
||||
)
|
||||
})
|
||||
|
||||
it('should error when qualities array is empty', async () => {
|
||||
await nextConfig.replace(
|
||||
'{ /* replaceme */ }',
|
||||
JSON.stringify({
|
||||
images: {
|
||||
qualities: [],
|
||||
},
|
||||
})
|
||||
)
|
||||
let stderr = ''
|
||||
|
||||
app = await launchApp(appDir, await findPort(), {
|
||||
onStderr(msg) {
|
||||
stderr += msg || ''
|
||||
},
|
||||
})
|
||||
await waitFor(1000)
|
||||
await killApp(app).catch(() => {})
|
||||
await nextConfig.restore()
|
||||
|
||||
expect(stderr).toContain(
|
||||
`Array must contain at least 1 element(s) at "images.qualities"`
|
||||
)
|
||||
})
|
||||
|
||||
it('should error when loader contains invalid value', async () => {
|
||||
await nextConfig.replace(
|
||||
'{ /* replaceme */ }',
|
||||
|
||||
@@ -98,6 +98,7 @@ function runTests(mode: 'dev' | 'server') {
|
||||
],
|
||||
minimumCacheTTL: 60,
|
||||
path: '/_next/image',
|
||||
qualities: undefined,
|
||||
sizes: [
|
||||
640, 750, 828, 1080, 1200, 1920, 2048, 3840, 16, 32, 48, 64, 96,
|
||||
128, 256, 384,
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,13 @@
|
||||
import Image from 'next/image'
|
||||
|
||||
import src from '../images/test.png'
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<main>
|
||||
<Image alt="q-100" id="q-100" quality={100} src={src} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,12 @@
|
||||
export const metadata = {
|
||||
title: 'Next.js',
|
||||
description: 'Generated by Next.js',
|
||||
}
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import Image from 'next/image'
|
||||
|
||||
import src from './images/test.png'
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<main>
|
||||
<Image alt="q-undefined" id="q-undefined" src={src} />
|
||||
<Image alt="q-42" id="q-42" quality={42} src={src} />
|
||||
<Image alt="q-69" id="q-69" quality={69} src={src} />
|
||||
<Image alt="q-88" id="q-88" quality={88} src={src} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
images: {
|
||||
qualities: [42, 69, 88],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
import {
|
||||
assertHasRedbox,
|
||||
assertNoRedbox,
|
||||
fetchViaHTTP,
|
||||
findPort,
|
||||
getImagesManifest,
|
||||
getRedboxHeader,
|
||||
killApp,
|
||||
launchApp,
|
||||
nextBuild,
|
||||
nextStart,
|
||||
} from 'next-test-utils'
|
||||
import webdriver from 'next-webdriver'
|
||||
import { join } from 'path'
|
||||
|
||||
const appDir = join(__dirname, '../')
|
||||
|
||||
let appPort: number
|
||||
let app: Awaited<ReturnType<typeof launchApp>>
|
||||
|
||||
async function getSrc(
|
||||
browser: Awaited<ReturnType<typeof webdriver>>,
|
||||
id: string
|
||||
) {
|
||||
const src = await browser.elementById(id).getAttribute('src')
|
||||
if (src) {
|
||||
const url = new URL(src, `http://localhost:${appPort}`)
|
||||
return url.href.slice(url.origin.length)
|
||||
}
|
||||
}
|
||||
|
||||
function runTests(mode: 'dev' | 'server') {
|
||||
it('should load img when quality is undefined', async () => {
|
||||
const browser = await webdriver(appPort, '/')
|
||||
if (mode === 'dev') {
|
||||
await assertNoRedbox(browser)
|
||||
}
|
||||
const url = await getSrc(browser, 'q-undefined')
|
||||
const res = await fetchViaHTTP(appPort, url)
|
||||
expect(res.status).toStrictEqual(200)
|
||||
expect(url).toContain('&q=69') // default to closest to 75
|
||||
})
|
||||
|
||||
it('should load img when quality 42', async () => {
|
||||
const browser = await webdriver(appPort, '/')
|
||||
if (mode === 'dev') {
|
||||
await assertNoRedbox(browser)
|
||||
}
|
||||
const url = await getSrc(browser, 'q-42')
|
||||
const res = await fetchViaHTTP(appPort, url)
|
||||
expect(res.status).toStrictEqual(200)
|
||||
})
|
||||
|
||||
it('should load img when quality 69', async () => {
|
||||
const browser = await webdriver(appPort, '/')
|
||||
if (mode === 'dev') {
|
||||
await assertNoRedbox(browser)
|
||||
}
|
||||
const url = await getSrc(browser, 'q-69')
|
||||
const res = await fetchViaHTTP(appPort, url)
|
||||
expect(res.status).toStrictEqual(200)
|
||||
})
|
||||
|
||||
it('should load img when quality 88', async () => {
|
||||
const browser = await webdriver(appPort, '/')
|
||||
if (mode === 'dev') {
|
||||
await assertNoRedbox(browser)
|
||||
}
|
||||
const url = await getSrc(browser, 'q-88')
|
||||
const res = await fetchViaHTTP(appPort, url)
|
||||
expect(res.status).toStrictEqual(200)
|
||||
})
|
||||
|
||||
it('should fail to load img when quality is 100', async () => {
|
||||
const page = '/invalid-quality'
|
||||
const browser = await webdriver(appPort, page)
|
||||
if (mode === 'dev') {
|
||||
await assertHasRedbox(browser)
|
||||
expect(await getRedboxHeader(browser)).toMatch(
|
||||
/Invalid quality prop (.+) on `next\/image` does not match `images.qualities` configured/g
|
||||
)
|
||||
} else {
|
||||
const url = await getSrc(browser, 'q-100')
|
||||
const res = await fetchViaHTTP(appPort, url)
|
||||
expect(res.status).toBe(400)
|
||||
}
|
||||
})
|
||||
|
||||
if (mode === 'server') {
|
||||
it('should build correct images-manifest.json', async () => {
|
||||
const manifest = getImagesManifest(appDir)
|
||||
expect(manifest).toEqual({
|
||||
version: 1,
|
||||
images: {
|
||||
contentDispositionType: 'attachment',
|
||||
contentSecurityPolicy:
|
||||
"script-src 'none'; frame-src 'none'; sandbox;",
|
||||
dangerouslyAllowSVG: false,
|
||||
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
|
||||
disableStaticImages: false,
|
||||
domains: [],
|
||||
formats: ['image/webp'],
|
||||
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
||||
loader: 'default',
|
||||
loaderFile: '',
|
||||
remotePatterns: [],
|
||||
localPatterns: undefined,
|
||||
minimumCacheTTL: 60,
|
||||
path: '/_next/image',
|
||||
qualities: [42, 69, 88],
|
||||
sizes: [
|
||||
640, 750, 828, 1080, 1200, 1920, 2048, 3840, 16, 32, 48, 64, 96,
|
||||
128, 256, 384,
|
||||
],
|
||||
unoptimized: false,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
describe('Image localPatterns config', () => {
|
||||
;(process.env.TURBOPACK_BUILD ? describe.skip : describe)(
|
||||
'development mode',
|
||||
() => {
|
||||
beforeAll(async () => {
|
||||
appPort = await findPort()
|
||||
app = await launchApp(appDir, appPort)
|
||||
})
|
||||
afterAll(async () => {
|
||||
await killApp(app)
|
||||
})
|
||||
|
||||
runTests('dev')
|
||||
}
|
||||
)
|
||||
;(process.env.TURBOPACK_DEV ? describe.skip : describe)(
|
||||
'production mode',
|
||||
() => {
|
||||
beforeAll(async () => {
|
||||
await nextBuild(appDir)
|
||||
appPort = await findPort()
|
||||
app = await nextStart(appDir, appPort)
|
||||
})
|
||||
afterAll(async () => {
|
||||
await killApp(app)
|
||||
})
|
||||
|
||||
runTests('server')
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -1623,6 +1623,7 @@ function runTests(mode: 'dev' | 'server') {
|
||||
localPatterns: undefined,
|
||||
minimumCacheTTL: 60,
|
||||
path: '/_next/image',
|
||||
qualities: undefined,
|
||||
sizes: [
|
||||
640, 750, 828, 1080, 1200, 1920, 2048, 3840, 16, 32, 48, 64, 96,
|
||||
128, 256, 384,
|
||||
|
||||
@@ -112,6 +112,7 @@ function runTests(url: string, mode: 'dev' | 'server') {
|
||||
localPatterns: undefined,
|
||||
minimumCacheTTL: 60,
|
||||
path: '/_next/image',
|
||||
qualities: undefined,
|
||||
sizes: [
|
||||
640, 750, 828, 1080, 1200, 1920, 2048, 3840, 16, 32, 48, 64, 96,
|
||||
128, 256, 384,
|
||||
|
||||
@@ -6,6 +6,7 @@ module.exports = phase => {
|
||||
domains: ['example.com', 'another.com'],
|
||||
remotePatterns: [{ protocol: 'https', hostname: '**.example.com' }],
|
||||
localPatterns: [{ pathname: '/assets/**', search: '' }],
|
||||
qualities: [25, 50, 75],
|
||||
},
|
||||
i18n: {
|
||||
locales: ['en','nl','fr'],
|
||||
|
||||
@@ -77,6 +77,7 @@ describe('config telemetry', () => {
|
||||
expect(event1).toMatch(/"imageRemotePatternsCount": 1/)
|
||||
expect(event1).toMatch(/"imageLocalPatternsCount": 2/)
|
||||
expect(event1).toMatch(/"imageSizes": "64,128,256,512,1024"/)
|
||||
expect(event1).toMatch(/"imageQualities": "25,50,75"/)
|
||||
expect(event1).toMatch(/"imageFormats": "image\/avif,image\/webp"/)
|
||||
expect(event1).toMatch(/"nextConfigOutput": null/)
|
||||
expect(event1).toMatch(/"trailingSlashEnabled": false/)
|
||||
@@ -123,6 +124,7 @@ describe('config telemetry', () => {
|
||||
expect(event2).toMatch(/"imageDomainsCount": 2/)
|
||||
expect(event2).toMatch(/"imageRemotePatternsCount": 1/)
|
||||
expect(event2).toMatch(/"imageLocalPatternsCount": 2/)
|
||||
expect(event2).toMatch(/"imageQualities": "25,50,75"/)
|
||||
expect(event2).toMatch(/"imageSizes": "64,128,256,512,1024"/)
|
||||
expect(event2).toMatch(/"nextConfigOutput": null/)
|
||||
expect(event2).toMatch(/"trailingSlashEnabled": false/)
|
||||
|
||||
Reference in New Issue
Block a user