feat(next/image): introduce preload prop and deprecated priority prop (#83351)

The Image component's existing `priority` prop is confusing since its
not obvious what it does.

In order to improve clarity, we are introducing a `preload` prop and
deprecating the `priority` prop.

We also updated the dev warning to no longer suggest using this prop for
the LCP and instead use `loading="eager"`.
This commit is contained in:
Steven
2025-09-04 13:50:17 +00:00
committed by GitHub
parent 6b55da593d
commit 07e9324016
11 changed files with 565 additions and 28 deletions
@@ -45,7 +45,7 @@ The following props are available:
| [`loader`](#loader) | `loader={imageLoader}` | Function | - |
| [`sizes`](#sizes) | `sizes="(max-width: 768px) 100vw, 33vw"` | String | - |
| [`quality`](#quality) | `quality={80}` | Integer (1-100) | - |
| [`priority`](#priority) | `priority={true}` | Boolean | - |
| [`preload`](#preload) | `preload={true}` | Boolean | - |
| [`placeholder`](#placeholder) | `placeholder="blur"` | String | - |
| [`style`](#style) | `style={{objectFit: "contain"}}` | Object | - |
| [`onLoadingComplete`](#onloadingcomplete) | `onLoadingComplete={img => done())}` | Function | Deprecated |
@@ -259,27 +259,35 @@ export default function ProfileImage() {
> **Good to know**: If you’re using the `style` prop to set a custom width, be sure to also set `height: 'auto'` to preserve the image’s aspect ratio.
#### `priority`
#### `preload`
A boolean that indicates if the image should be preloaded.
```jsx
// Default priority is false
<Image priority={false} />
// Default preload is false
<Image preload={false} />
```
- `true`: [Preloads](https://web.dev/preload-responsive-images/) the image. Disables lazy loading.
- `false`: Lazy loads the image.
- `true`: [Preloads](https://web.dev/preload-responsive-images/) the image by inserting a `<link>` in the `<head>`.
- `false`: Does not preload the image.
**When to use it:**
- The image is above the fold.
- The image is the [Largest Contentful Paint (LCP)](https://nextjs.org/learn/seo/web-performance/lcp) element.
- You want to improve the initial loading performance of your page.
- The image is above the fold, typically the hero image.
- You want to begin loading the image in the `<head>`, before its discovered later in the `<body>`.
**When not to use it:**
- When the `loading` prop is used (will trigger warnings).
- When you have multiple images that could be considered the [Largest Contentful Paint (LCP)](https://nextjs.org/learn/seo/web-performance/lcp) element depending on the viewport.
- When the `loading` property is used.
- When the `fetchPriority` property is used.
In most cases, you should use `loading="eager"` or `fetchPriority="high"` instead of `preload`.
#### `priority`
Starting with Next.js 16, the `priority` property has been deprecated in favor of the [`preload`](#preload) property in order to make the behavior clear.
#### `loading`
@@ -908,7 +916,7 @@ This `next/image` component uses browser native [lazy loading](https://caniuse.c
- [Safari 15 - 16.3](https://bugs.webkit.org/show_bug.cgi?id=243601) display a gray border while loading. Safari 16.4 [fixed this issue](https://webkit.org/blog/13966/webkit-features-in-safari-16-4/#:~:text=Now%20in%20Safari%2016.4%2C%20a%20gray%20line%20no%20longer%20appears%20to%20mark%20the%20space%20where%20a%20lazy%2Dloaded%20image%20will%20appear%20once%20it%E2%80%99s%20been%20loaded.). Possible solutions:
- Use CSS `@supports (font: -apple-system-body) and (-webkit-appearance: none) { img[loading="lazy"] { clip-path: inset(0.6px) } }`
- Use [`priority`](#priority) if the image is above the fold
- Use [`loading="eager"`](#loading) if the image is above the fold
- [Firefox 67+](https://bugzilla.mozilla.org/show_bug.cgi?id=1556156) displays a white background while loading. Possible solutions:
- Enable [AVIF `formats`](#formats)
- Use [`placeholder`](#placeholder)
@@ -1155,7 +1163,7 @@ If you want to display a different image for light and dark mode, you can create
import styles from './theme-image.module.css'
import Image, { ImageProps } from 'next/image'
type Props = Omit<ImageProps, 'src' | 'priority' | 'loading'> & {
type Props = Omit<ImageProps, 'src' | 'preload' | 'loading'> & {
srcLight: string
srcDark: string
}
@@ -1188,7 +1196,7 @@ const ThemeImage = (props) => {
}
```
> **Good to know**: The default behavior of `loading="lazy"` ensures that only the correct image is loaded. You cannot use `priority` or `loading="eager"` because that would cause both images to load. Instead, you can use [`fetchPriority="high"`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/fetchPriority).
> **Good to know**: The default behavior of `loading="lazy"` ensures that only the correct image is loaded. You cannot use `preload` or `loading="eager"` because that would cause both images to load. Instead, you can use [`fetchPriority="high"`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/fetchPriority).
Try it out:
@@ -1269,7 +1277,7 @@ export default function Home() {
| Version | Changes |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `v16.0.0` | `qualities` default configuration changed to `[75]`. |
| `v16.0.0` | `qualities` default configuration changed to `[75]`, `preload` prop added, `priority` prop deprecated. |
| `v15.3.0` | `remotePatterns` added support for array of `URL` objects. |
| `v15.0.0` | `contentDispositionType` configuration default changed to `attachment`. |
| `v14.2.23` | `qualities` configuration added. |
+3 -1
View File
@@ -799,5 +799,7 @@
"798": "expected a result to be returned",
"799": "expected a page response, got %s",
"800": "`experimental.rdcForNavigations` is enabled, but `experimental.ppr` is not.",
"801": "> `pages` and `app` directories should be under the same folder"
"801": "> `pages` and `app` directories should be under the same folder",
"802": "Image with src \"%s\" has both \"preload\" and \"priority\" properties. Only \"preload\" should be used.",
"803": "Image with src \"%s\" has both \"preload\" and \"loading='lazy'\" properties. Only one should be used."
}
+1 -1
View File
@@ -409,7 +409,7 @@ export const Image = forwardRef<HTMLImageElement | null, ImageProps>(
ref={forwardedRef}
/>
}
{imgMeta.priority ? (
{imgMeta.preload ? (
<ImagePreload
isAppRouter={isAppRouter}
imgAttributes={imgAttributes}
+29 -9
View File
@@ -35,6 +35,11 @@ export type ImageProps = Omit<
fill?: boolean
loader?: ImageLoader
quality?: number | `${number}`
preload?: boolean
/**
* @deprecated Use `preload` prop instead.
* See https://nextjs.org/docs/app/api-reference/components/image#preload
*/
priority?: boolean
loading?: LoadingValue
placeholder?: PlaceholderValue
@@ -139,7 +144,7 @@ function isStaticImport(src: string | StaticImport): src is StaticImport {
const allImgs = new Map<
string,
{ src: string; priority: boolean; placeholder: PlaceholderValue }
{ src: string; loading: LoadingValue; placeholder: PlaceholderValue }
>()
let perfObserver: PerformanceObserver | undefined
@@ -261,6 +266,7 @@ export function getImgProps(
sizes,
unoptimized = false,
priority = false,
preload = false,
loading,
className,
quality,
@@ -292,7 +298,7 @@ export function getImgProps(
props: ImgProps
meta: {
unoptimized: boolean
priority: boolean
preload: boolean
placeholder: NonNullable<ImageProps['placeholder']>
fill: boolean
}
@@ -408,7 +414,9 @@ export function getImgProps(
src = typeof src === 'string' ? src : staticSrc
let isLazy =
!priority && (loading === 'lazy' || typeof loading === 'undefined')
!priority &&
!preload &&
(loading === 'lazy' || typeof loading === 'undefined')
if (!src || src.startsWith('data:') || src.startsWith('blob:')) {
// https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
unoptimized = true
@@ -516,6 +524,16 @@ export function getImgProps(
`Image with src "${src}" has both "priority" and "loading='lazy'" properties. Only one should be used.`
)
}
if (preload && loading === 'lazy') {
throw new Error(
`Image with src "${src}" has both "preload" and "loading='lazy'" properties. Only one should be used.`
)
}
if (preload && priority) {
throw new Error(
`Image with src "${src}" has both "preload" and "priority" properties. Only "preload" should be used.`
)
}
if (
placeholder !== 'empty' &&
placeholder !== 'blur' &&
@@ -626,15 +644,15 @@ export function getImgProps(
const lcpImage = allImgs.get(imgSrc)
if (
lcpImage &&
!lcpImage.priority &&
lcpImage.loading === 'lazy' &&
lcpImage.placeholder === 'empty' &&
!lcpImage.src.startsWith('data:') &&
!lcpImage.src.startsWith('blob:')
) {
// https://web.dev/lcp/#measure-lcp-in-javascript
warnOnce(
`Image with src "${lcpImage.src}" was detected as the Largest Contentful Paint (LCP). Please add the "priority" property if this image is above the fold.` +
`\nRead more: https://nextjs.org/docs/api-reference/next/image#priority`
`Image with src "${lcpImage.src}" was detected as the Largest Contentful Paint (LCP). Please add the \`loading="eager"\` property if this image is above the fold.` +
`\nRead more: https://nextjs.org/docs/app/api-reference/components/image#loading`
)
}
}
@@ -722,6 +740,8 @@ export function getImgProps(
loader,
})
const loadingFinal = isLazy ? 'lazy' : loading
if (process.env.NODE_ENV !== 'production') {
if (typeof window !== 'undefined') {
let fullUrl: URL
@@ -730,13 +750,13 @@ export function getImgProps(
} catch (e) {
fullUrl = new URL(imgAttributes.src, window.location.href)
}
allImgs.set(fullUrl.href, { src, priority, placeholder })
allImgs.set(fullUrl.href, { src, loading: loadingFinal, placeholder })
}
}
const props: ImgProps = {
...rest,
loading: isLazy ? 'lazy' : loading,
loading: loadingFinal,
fetchPriority,
width: widthInt,
height: heightInt,
@@ -747,6 +767,6 @@ export function getImgProps(
srcSet: imgAttributes.srcSet,
src: overrideSrc || imgAttributes.src,
}
const meta = { unoptimized, priority, placeholder, fill }
const meta = { unoptimized, preload: preload || priority, placeholder, fill }
return { props, meta }
}
@@ -0,0 +1,82 @@
import React from 'react'
import Image from 'next/image'
const Page = () => {
return (
<div>
<p>Preload Page</p>
<Image
preload
id="basic-image"
alt="basic-image"
src="/test.jpg"
width="400"
height="400"
></Image>
<Image
preload
id="basic-image-crossorigin"
alt="basic-image-crossorigin"
src="/test.webp"
width="400"
height="400"
crossOrigin="use-credentials"
></Image>
<Image
preload
id="basic-image-referrerpolicy"
alt="basic-image-referrerpolicy"
src="/test.png"
width="400"
height="400"
referrerPolicy="no-referrer"
></Image>
<Image
loading="eager"
id="load-eager"
alt="load-eager"
src="/test.png"
width="200"
height="200"
></Image>
<Image
preload
id="responsive1"
alt="responsive1"
src="/wide.png"
width="1200"
height="700"
sizes="100vw"
/>
<Image
preload
id="responsive2"
alt="responsive2"
src="/wide.png"
width="1200"
height="700"
sizes="100vw"
/>
<Image
id="pri-low"
alt="pri-low"
src="/test.webp"
width="100"
height="100"
fetchPriority="low"
/>
<p id="stubtext">This is the preload page</p>
<div style={{ height: '1000vh' }} />
<Image
preload
id="belowthefold"
src="/test.tiff"
width="400"
height="400"
alt=""
/>
</div>
)
}
export default Page
@@ -255,6 +255,157 @@ function runTests(mode: 'dev' | 'server') {
}
})
it('should work with preload prop', async () => {
let browser
try {
browser = await webdriver(appPort, '/preload')
await check(async () => {
const result = await browser.eval(
`document.getElementById('basic-image').naturalWidth`
)
if (result === 0) {
throw new Error('Incorrectly loaded image')
}
return 'result-correct'
}, /result-correct/)
const links = await browser.elementsByCss('link[rel=preload][as=image]')
const entries = []
for (const link of links) {
const fetchpriority = await link.getAttribute('fetchpriority')
const imagesrcset = await link.getAttribute('imagesrcset')
const imagesizes = await link.getAttribute('imagesizes')
const crossorigin = await link.getAttribute('crossorigin')
const referrerpolicy = await link.getAttribute('referrerPolicy')
entries.push({
fetchpriority,
imagesrcset,
imagesizes,
crossorigin,
referrerpolicy,
})
}
expect(
entries.find(
(item) =>
item.imagesrcset ===
'/_next/image?url=%2Ftest.webp&w=640&q=75 1x, /_next/image?url=%2Ftest.webp&w=828&q=75 2x'
)
).toEqual({
fetchpriority: '',
imagesizes: '',
imagesrcset:
'/_next/image?url=%2Ftest.webp&w=640&q=75 1x, /_next/image?url=%2Ftest.webp&w=828&q=75 2x',
crossorigin: 'use-credentials',
referrerpolicy: '',
})
expect(
entries.find(
(item) =>
item.imagesrcset ===
'/_next/image?url=%2Fwide.png&w=640&q=75 640w, /_next/image?url=%2Fwide.png&w=750&q=75 750w, /_next/image?url=%2Fwide.png&w=828&q=75 828w, /_next/image?url=%2Fwide.png&w=1080&q=75 1080w, /_next/image?url=%2Fwide.png&w=1200&q=75 1200w, /_next/image?url=%2Fwide.png&w=1920&q=75 1920w, /_next/image?url=%2Fwide.png&w=2048&q=75 2048w, /_next/image?url=%2Fwide.png&w=3840&q=75 3840w'
)
).toEqual({
fetchpriority: '',
imagesizes: '100vw',
imagesrcset:
'/_next/image?url=%2Fwide.png&w=640&q=75 640w, /_next/image?url=%2Fwide.png&w=750&q=75 750w, /_next/image?url=%2Fwide.png&w=828&q=75 828w, /_next/image?url=%2Fwide.png&w=1080&q=75 1080w, /_next/image?url=%2Fwide.png&w=1200&q=75 1200w, /_next/image?url=%2Fwide.png&w=1920&q=75 1920w, /_next/image?url=%2Fwide.png&w=2048&q=75 2048w, /_next/image?url=%2Fwide.png&w=3840&q=75 3840w',
crossorigin: '',
referrerpolicy: '',
})
expect(
entries.find(
(item) =>
item.imagesrcset ===
'/_next/image?url=%2Ftest.png&w=640&q=75 1x, /_next/image?url=%2Ftest.png&w=828&q=75 2x'
)
).toEqual({
fetchpriority: '',
imagesizes: '',
imagesrcset:
'/_next/image?url=%2Ftest.png&w=640&q=75 1x, /_next/image?url=%2Ftest.png&w=828&q=75 2x',
crossorigin: '',
referrerpolicy: 'no-referrer',
})
expect(
entries.find(
(item) =>
item.imagesrcset ===
'/_next/image?url=%2Ftest.tiff&w=640&q=75 1x, /_next/image?url=%2Ftest.tiff&w=828&q=75 2x'
)
).toEqual({
fetchpriority: '',
imagesizes: '',
imagesrcset:
'/_next/image?url=%2Ftest.tiff&w=640&q=75 1x, /_next/image?url=%2Ftest.tiff&w=828&q=75 2x',
crossorigin: '',
referrerpolicy: '',
})
// When preload={true}, we should _not_ set loading="lazy"
expect(
await browser.elementById('basic-image').getAttribute('loading')
).toBe(null)
expect(
await browser.elementById('load-eager').getAttribute('loading')
).toBe('eager')
expect(
await browser.elementById('responsive1').getAttribute('loading')
).toBe(null)
expect(
await browser.elementById('responsive2').getAttribute('loading')
).toBe(null)
// When preload={true}, we should not set fetchpriority="high"
expect(
await browser.elementById('basic-image').getAttribute('fetchpriority')
).toBe(null)
expect(
await browser.elementById('load-eager').getAttribute('fetchpriority')
).toBe(null)
expect(
await browser.elementById('responsive1').getAttribute('fetchpriority')
).toBe(null)
expect(
await browser.elementById('responsive2').getAttribute('fetchpriority')
).toBe(null)
// Setting fetchPriority="low" directly should pass-through to <img>
expect(
await browser.elementById('pri-low').getAttribute('fetchpriority')
).toBe('low')
expect(await browser.elementById('pri-low').getAttribute('loading')).toBe(
'lazy'
)
expect(
await browser.elementById('belowthefold').getAttribute('fetchpriority')
).toBe(null)
expect(
await browser.elementById('belowthefold').getAttribute('loading')
).toBe(null)
const warnings = (await browser.log())
.map((log) => log.message)
.join('\n')
expect(warnings).not.toMatch(
/was detected as the Largest Contentful Paint/gm
)
expect(warnings).not.toMatch(/React does not recognize the (.+) prop/gm)
} finally {
if (browser) {
await browser.close()
}
}
})
it('should not pass through user-provided srcset (causing a flash)', async () => {
const html = await renderViaHTTP(appPort, '/drop-srcset')
const $html = cheerio.load(html)
@@ -1077,8 +1228,8 @@ function runTests(mode: 'dev' | 'server') {
expect(warnings).not.toMatch(/cannot appear as a descendant/gm)
})
it('should warn when priority prop is missing on LCP image', async () => {
let browser = await webdriver(appPort, '/priority-missing-warning')
it('should warn when preload prop is missing on LCP image', async () => {
let browser = await webdriver(appPort, '/preload-missing-warning')
try {
// Wait for image to load:
await check(async () => {
@@ -0,0 +1,82 @@
import React from 'react'
import Image from 'next/image'
const Page = () => {
return (
<div>
<p>Preload Page</p>
<Image
preload
id="basic-image"
alt="basic-image"
src="/test.jpg"
width="400"
height="400"
></Image>
<Image
preload
id="basic-image-crossorigin"
alt="basic-image-crossorigin"
src="/test.webp"
width="400"
height="400"
crossOrigin="use-credentials"
></Image>
<Image
preload
id="basic-image-referrerpolicy"
alt="basic-image-referrerpolicy"
src="/test.png"
width="400"
height="400"
referrerPolicy="no-referrer"
></Image>
<Image
loading="eager"
id="load-eager"
alt="load-eager"
src="/test.png"
width="200"
height="200"
></Image>
<Image
preload
id="responsive1"
alt="responsive1"
src="/wide.png"
width="1200"
height="700"
sizes="100vw"
/>
<Image
preload
id="responsive2"
alt="responsive2"
src="/wide.png"
width="1200"
height="700"
sizes="100vw"
/>
<Image
id="pri-low"
alt="pri-low"
src="/test.webp"
width="100"
height="100"
fetchPriority="low"
/>
<p id="stubtext">This is the preload page</p>
<div style={{ height: '1000vh' }} />
<Image
preload
id="belowthefold"
src="/test.tiff"
width="400"
height="400"
alt=""
/>
</div>
)
}
export default Page
@@ -257,6 +257,157 @@ function runTests(mode) {
}
})
it('should work with preload prop', async () => {
let browser
try {
browser = await webdriver(appPort, '/preload')
await check(async () => {
const result = await browser.eval(
`document.getElementById('basic-image').naturalWidth`
)
if (result === 0) {
throw new Error('Incorrectly loaded image')
}
return 'result-correct'
}, /result-correct/)
const links = await browser.elementsByCss('link[rel=preload][as=image]')
const entries = []
for (const link of links) {
const fetchpriority = await link.getAttribute('fetchpriority')
const imagesrcset = await link.getAttribute('imagesrcset')
const imagesizes = await link.getAttribute('imagesizes')
const crossorigin = await link.getAttribute('crossorigin')
const referrerpolicy = await link.getAttribute('referrerPolicy')
entries.push({
fetchpriority,
imagesrcset,
imagesizes,
crossorigin,
referrerpolicy,
})
}
expect(
entries.find(
(item) =>
item.imagesrcset ===
'/_next/image?url=%2Ftest.webp&w=640&q=75 1x, /_next/image?url=%2Ftest.webp&w=828&q=75 2x'
)
).toEqual({
fetchpriority: '',
imagesizes: '',
imagesrcset:
'/_next/image?url=%2Ftest.webp&w=640&q=75 1x, /_next/image?url=%2Ftest.webp&w=828&q=75 2x',
crossorigin: 'use-credentials',
referrerpolicy: '',
})
expect(
entries.find(
(item) =>
item.imagesrcset ===
'/_next/image?url=%2Fwide.png&w=640&q=75 640w, /_next/image?url=%2Fwide.png&w=750&q=75 750w, /_next/image?url=%2Fwide.png&w=828&q=75 828w, /_next/image?url=%2Fwide.png&w=1080&q=75 1080w, /_next/image?url=%2Fwide.png&w=1200&q=75 1200w, /_next/image?url=%2Fwide.png&w=1920&q=75 1920w, /_next/image?url=%2Fwide.png&w=2048&q=75 2048w, /_next/image?url=%2Fwide.png&w=3840&q=75 3840w'
)
).toEqual({
fetchpriority: '',
imagesizes: '100vw',
imagesrcset:
'/_next/image?url=%2Fwide.png&w=640&q=75 640w, /_next/image?url=%2Fwide.png&w=750&q=75 750w, /_next/image?url=%2Fwide.png&w=828&q=75 828w, /_next/image?url=%2Fwide.png&w=1080&q=75 1080w, /_next/image?url=%2Fwide.png&w=1200&q=75 1200w, /_next/image?url=%2Fwide.png&w=1920&q=75 1920w, /_next/image?url=%2Fwide.png&w=2048&q=75 2048w, /_next/image?url=%2Fwide.png&w=3840&q=75 3840w',
crossorigin: '',
referrerpolicy: '',
})
expect(
entries.find(
(item) =>
item.imagesrcset ===
'/_next/image?url=%2Ftest.png&w=640&q=75 1x, /_next/image?url=%2Ftest.png&w=828&q=75 2x'
)
).toEqual({
fetchpriority: '',
imagesizes: '',
imagesrcset:
'/_next/image?url=%2Ftest.png&w=640&q=75 1x, /_next/image?url=%2Ftest.png&w=828&q=75 2x',
crossorigin: '',
referrerpolicy: 'no-referrer',
})
expect(
entries.find(
(item) =>
item.imagesrcset ===
'/_next/image?url=%2Ftest.tiff&w=640&q=75 1x, /_next/image?url=%2Ftest.tiff&w=828&q=75 2x'
)
).toEqual({
fetchpriority: '',
imagesizes: '',
imagesrcset:
'/_next/image?url=%2Ftest.tiff&w=640&q=75 1x, /_next/image?url=%2Ftest.tiff&w=828&q=75 2x',
crossorigin: '',
referrerpolicy: '',
})
// When preload={true}, we should _not_ set loading="lazy"
expect(
await browser.elementById('basic-image').getAttribute('loading')
).toBe(null)
expect(
await browser.elementById('load-eager').getAttribute('loading')
).toBe('eager')
expect(
await browser.elementById('responsive1').getAttribute('loading')
).toBe(null)
expect(
await browser.elementById('responsive2').getAttribute('loading')
).toBe(null)
// When preload={true}, we not should set fetchpriority="high"
expect(
await browser.elementById('basic-image').getAttribute('fetchpriority')
).toBe(null)
expect(
await browser.elementById('load-eager').getAttribute('fetchpriority')
).toBe(null)
expect(
await browser.elementById('responsive1').getAttribute('fetchpriority')
).toBe(null)
expect(
await browser.elementById('responsive2').getAttribute('fetchpriority')
).toBe(null)
// Setting fetchPriority="low" directly should pass-through to <img>
expect(
await browser.elementById('pri-low').getAttribute('fetchpriority')
).toBe('low')
expect(await browser.elementById('pri-low').getAttribute('loading')).toBe(
'lazy'
)
expect(
await browser.elementById('belowthefold').getAttribute('fetchpriority')
).toBe(null)
expect(
await browser.elementById('belowthefold').getAttribute('loading')
).toBe(null)
const warnings = (await browser.log())
.map((log) => log.message)
.join('\n')
expect(warnings).not.toMatch(
/was detected as the Largest Contentful Paint/gm
)
expect(warnings).not.toMatch(/React does not recognize the (.+) prop/gm)
} finally {
if (browser) {
await browser.close()
}
}
})
it('should not pass through user-provided srcset (causing a flash)', async () => {
const html = await renderViaHTTP(appPort, '/drop-srcset')
const $html = cheerio.load(html)
@@ -1079,8 +1230,8 @@ function runTests(mode) {
expect(warnings).not.toMatch(/cannot appear as a descendant/gm)
})
it('should warn when priority prop is missing on LCP image', async () => {
let browser = await webdriver(appPort, '/priority-missing-warning')
it('should warn when preload prop is missing on LCP image', async () => {
let browser = await webdriver(appPort, '/preload-missing-warning')
try {
// Wait for image to load:
await check(async () => {
@@ -61,6 +61,47 @@ describe('getImageProps()', () => {
expect(props.srcSet).toBeString()
})
it('should handle preload', async () => {
const { props } = getImageProps({
alt: 'a nice desc',
id: 'my-image',
src: '/test.png',
width: 100,
height: 200,
preload: true,
})
expect(warningMessages).toStrictEqual([])
expect(Object.entries(props)).toStrictEqual([
['alt', 'a nice desc'],
['id', 'my-image'],
['width', 100],
['height', 200],
['decoding', 'async'],
['style', { color: 'transparent' }],
[
'srcSet',
'/_next/image?url=%2Ftest.png&w=128&q=75 1x, /_next/image?url=%2Ftest.png&w=256&q=75 2x',
],
['src', '/_next/image?url=%2Ftest.png&w=256&q=75'],
])
})
it('should error when both priority and preload are used', async () => {
expect(() =>
getImageProps({
alt: 'a nice desc',
id: 'my-image',
src: '/test.png',
width: 100,
height: 200,
preload: true,
priority: true,
})
).toThrow(
'Image with src "/test.png" has both "preload" and "priority" properties. Only "preload" should be used.'
)
})
it('should handle priority', async () => {
const { props } = getImageProps({
alt: 'a nice desc',