Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(next/image): add overrideSrc prop #64221

Merged
merged 8 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/02-app/02-api-reference/01-components/image.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Here's a summary of the props available for the Image Component:
| [`onError`](#onerror) | `onError(event => fail()}` | Function | - |
| [`loading`](#loading) | `loading="lazy"` | String | - |
| [`blurDataURL`](#blurdataurl) | `blurDataURL="data:image/jpeg..."` | String | - |
| [`overrideSrc`](#overridesrc) | `overrideSrc="/seo.png"` | String | - |
</div>

## Required Props
Expand Down Expand Up @@ -424,6 +425,44 @@ module.exports = {
}
```

### `overrideSrc`

When providing the `src` prop to the `<Image>` component, both the `srcset` and `src` attributes are generated automatically for the resulting `<img>`.

```jsx
{
/* input */
}
;<Image src="/me.jpg" />
leerob marked this conversation as resolved.
Show resolved Hide resolved

{
/* output */
}
;<img
srcset="/_next/image?url=%2Fme.jpg&w=640&q=75 1x, /_next/image?url=%2Fme.jpg&w=828&q=75 2x"
src="/_next/image?url=%2Fme.jpg&w=828&q=75"
/>
```

In some cases, it is not desirable to have the `src` attribute generated and you may wish to override it using the `overrideSrc` prop.

For example, when upgrading an existing website from `<img>` to `<Image>`, you may wish to maintain the same `src` attribute for SEO purposes such as image ranking or avoiding recrawl.

```jsx
{
/* input */
}
;<Image src="/me.jpg" overrideSrc="/override.jpg" />

{
/* output */
}
;<img
srcset="/_next/image?url=%2Fme.jpg&w=640&q=75 1x, /_next/image?url=%2Fme.jpg&w=828&q=75 2x"
src="/override.jpg"
/>
```

### Other Props

Other properties on the `<Image />` component will be passed to the underlying
Expand Down Expand Up @@ -963,6 +1002,7 @@ This `next/image` component uses browser native [lazy loading](https://caniuse.c

| Version | Changes |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `v14.2.0` | `overrideSrc` prop added. |
| `v14.1.0` | `getImageProps()` is stable. |
| `v14.0.0` | `onLoadingComplete` prop and `domains` config deprecated. |
| `v13.4.14` | `placeholder` prop support for `data:/image...` |
Expand Down
4 changes: 3 additions & 1 deletion packages/next/src/shared/lib/get-img-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type ImageProps = Omit<
placeholder?: PlaceholderValue
blurDataURL?: string
unoptimized?: boolean
overrideSrc?: string
/**
* @deprecated Use `onLoad` instead.
* @see https://nextjs.org/docs/app/api-reference/components/image#onload
Expand Down Expand Up @@ -245,6 +246,7 @@ export function getImgProps(
height,
fill = false,
style,
overrideSrc,
onLoad,
onLoadingComplete,
placeholder = 'empty',
Expand Down Expand Up @@ -671,7 +673,7 @@ export function getImgProps(
style: { ...imgStyle, ...placeholderStyle },
sizes: imgAttributes.sizes,
srcSet: imgAttributes.srcSet,
src: imgAttributes.src,
src: overrideSrc || imgAttributes.src,
}
const meta = { unoptimized, priority, placeholder, fill }
return { props, meta }
Expand Down
20 changes: 20 additions & 0 deletions test/integration/next-image-new/app-dir/app/override-src/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react'
import Image from 'next/image'

const Page = () => {
return (
<div>
<p>Override Src</p>
<Image
id="override-src"
alt=""
src="/test.jpg"
overrideSrc="/myoverride"
width={400}
height={400}
/>
</div>
)
}

export default Page
23 changes: 23 additions & 0 deletions test/integration/next-image-new/app-dir/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,29 @@ function runTests(mode) {
}
})

it('should work when using overrideSrc prop', async () => {
const browser = await webdriver(appPort, '/override-src')
await check(async () => {
const result = await browser.eval(
`document.getElementById('override-src').width`
)
if (result === 0) {
throw new Error('Incorrectly loaded image')
}

return 'result-correct'
}, /result-correct/)

await check(
() => browser.eval(`document.getElementById('override-src').currentSrc`),
/test(.*)jpg/
)
await check(
() => browser.eval(`document.getElementById('override-src').src`),
/myoverride/
)
})

it('should work with sizes and automatically use responsive srcset', async () => {
const browser = await webdriver(appPort, '/sizes')
const id = 'sizes1'
Expand Down
20 changes: 20 additions & 0 deletions test/integration/next-image-new/default/pages/override-src.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react'
import Image from 'next/image'

const Page = () => {
return (
<div>
<p>Override Src</p>
<Image
id="override-src"
alt=""
src="/test.jpg"
overrideSrc="/myoverride"
width={400}
height={400}
/>
</div>
)
}

export default Page
23 changes: 23 additions & 0 deletions test/integration/next-image-new/default/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,29 @@ function runTests(mode) {
}
})

it('should work when using overrideSrc prop', async () => {
const browser = await webdriver(appPort, '/override-src')
await check(async () => {
const result = await browser.eval(
`document.getElementById('override-src').width`
)
if (result === 0) {
throw new Error('Incorrectly loaded image')
}

return 'result-correct'
}, /result-correct/)

await check(
() => browser.eval(`document.getElementById('override-src').currentSrc`),
/test(.*)jpg/
)
await check(
() => browser.eval(`document.getElementById('override-src').src`),
/myoverride/
)
})

it('should work with sizes and automatically use responsive srcset', async () => {
const browser = await webdriver(appPort, '/sizes')
const id = 'sizes1'
Expand Down
23 changes: 23 additions & 0 deletions test/unit/next-image-get-img-props.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,4 +254,27 @@ describe('getImageProps()', () => {
['src', '/_next/image?url=%2Ftest.png&w=256&q=75'],
])
})
it('should override src', async () => {
const { props } = getImageProps({
alt: 'a nice desc',
src: '/test.png',
overrideSrc: '/override.png',
width: 100,
height: 200,
})
expect(warningMessages).toStrictEqual([])
expect(Object.entries(props)).toStrictEqual([
['alt', 'a nice desc'],
['loading', 'lazy'],
['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', '/override.png'],
])
})
})