Skip to content

Commit

Permalink
Merge branch 'canary' into new-api-signature
Browse files Browse the repository at this point in the history
  • Loading branch information
kodiakhq[bot] committed Oct 25, 2021
2 parents e92409e + 4782cac commit d35b823
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 1 deletion.
2 changes: 1 addition & 1 deletion docs/api-reference/next/image.md
Expand Up @@ -134,7 +134,7 @@ The quality of the optimized image, an integer between `1` and `100` where `100`
When true, the image will be considered high priority and
[preload](https://web.dev/preload-responsive-images/). Lazy loading is automatically disabled for images using `priority`.

You should use the `priority` attribute on any image which you suspect will be the [Largest Contentful Paint (LCP) element](https://nextjs.org/learn/seo/web-performance/lcp). It may be appropriate to have multiple priority images, as different images may be the LCP element for different viewport sizes.
You should use the `priority` property on any image detected as the [Largest Contentful Paint (LCP)](https://nextjs.org/learn/seo/web-performance/lcp) element. It may be appropriate to have multiple priority images, as different images may be the LCP element for different viewport sizes.

Should only be used when the image is visible above the fold. Defaults to `false`.

Expand Down
38 changes: 38 additions & 0 deletions docs/basic-features/image-optimization.md
Expand Up @@ -113,6 +113,44 @@ The default loader for Next.js applications uses the built-in Image Optimization

Loaders can be defined per-image, or at the application level.

### Priority

You should add the `priority` property to the image that will be the [Largest Contentful Paint (LCP) element](https://web.dev/lcp/#what-elements-are-considered) for each page. Doing so allows Next.js to specially prioritize the image for loading (e.g. through preload tags or priority hints), leading to a meaningful boost in LCP.

The LCP element is typically the largest image or text block visible within the viewport of the page. You can verify which element this is by running the following code in the console of your page and looking at the latest result:

```javascript
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.log('LCP candidate:', entry.startTime, entry.element)
}
}).observe({ type: 'largest-contentful-paint', buffered: true })
```

Once you've identified the LCP image, you can add the property like this:

```jsx
import Image from 'next/image'

export default function Home() {
return (
<>
<h1>My Homepage</h1>
<Image
src="/me.png"
alt="Picture of the author"
width={500}
height={500}
priority
/>
<p>Welcome to my homepage!</p>
</>
)
}
```

See more about priority in the [`next/image` component documentation](/docs/api-reference/next/image.md#priority).

## Image Sizing

One of the ways that images most commonly hurt performance is through _layout shift_, where the image pushes other elements around on the page as it loads in. This performance problem is so annoying to users that it has its own Core Web Vital, called [Cumulative Layout Shift](https://web.dev/cls/). The way to avoid image-based layout shifts is to [always size your images](https://web.dev/optimize-cls/#images-without-dimensions). This allows the browser to reserve precisely enough space for the image before it loads.
Expand Down
6 changes: 6 additions & 0 deletions docs/upgrading.md
Expand Up @@ -38,6 +38,12 @@ module.exports = {

Minification using SWC is an opt-in flag to ensure it can be tested against more real-world Next.js applications before it becomes the default in Next.js 12.1. If you have feedback about minification, please leave it on [the feedback thread](https://github.com/vercel/next.js/discussions/30237).

### Improvements to styled-jsx CSS parsing

On top of the Rust-based compiler we've implemented a new CSS parser based on the CSS parser that was used for the styled-jsx Babel transform. This new parser has improved handling of CSS and now errors when invalid CSS is used that would previously slip through and cause unexpected behavior.

Because of this change invalid CSS will throw an error during development and `next build`. This change only affects styled-jsx usage.

### `next/image` changed wrapping element

`next/image` now renders the `<img>` inside a `<span>` instead of `<div>`.
Expand Down
41 changes: 41 additions & 0 deletions packages/next/client/image.tsx
Expand Up @@ -10,6 +10,11 @@ import {
import { useIntersection } from './use-intersection'

const loadedImageURLs = new Set<string>()
const allImgs = new Map<
string,
{ src: string; priority: boolean; placeholder: string }
>()
let perfObserver: PerformanceObserver | undefined
const emptyDataURL =
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'

Expand Down Expand Up @@ -450,6 +455,30 @@ export default function Image({
`\nRead more: https://nextjs.org/docs/messages/next-image-missing-loader-width`
)
}

if (typeof window !== 'undefined' && !perfObserver) {
perfObserver = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
// @ts-ignore - missing "LargestContentfulPaint" class with "element" prop
const imgSrc = entry?.element?.src || ''
const lcpImage = allImgs.get(imgSrc)
if (
lcpImage &&
!lcpImage.priority &&
lcpImage.placeholder !== 'blur' &&
!lcpImage.src.startsWith('data:') &&
!lcpImage.src.startsWith('blob:')
) {
// https://web.dev/lcp/#measure-lcp-in-javascript
console.warn(
`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`
)
}
}
})
perfObserver.observe({ type: 'largest-contentful-paint', buffered: true })
}
}

const [setRef, isIntersected] = useIntersection<HTMLImageElement>({
Expand Down Expand Up @@ -580,6 +609,18 @@ export default function Image({

let srcString: string = src

if (process.env.NODE_ENV !== 'production') {
if (typeof window !== 'undefined') {
let fullUrl: URL
try {
fullUrl = new URL(imgAttributes.src)
} catch (e) {
fullUrl = new URL(imgAttributes.src, window.location.href)
}
allImgs.set(fullUrl.href, { src, priority, placeholder })
}
}

return (
<span style={wrapperStyle}>
{hasSizer ? (
Expand Down
@@ -0,0 +1,27 @@
import React from 'react'
import Image from 'next/image'

const Page = () => {
return (
<div>
<h1>Priority Missing Warning Page</h1>
<Image
id="responsive"
layout="responsive"
src="/wide.png"
width="1200"
height="700"
/>
<Image
id="fixed"
layout="fixed"
src="/test.jpg"
width="400"
height="400"
/>
<footer>Priority Missing Warning Footer</footer>
</div>
)
}

export default Page
36 changes: 36 additions & 0 deletions test/integration/image-component/default/test/index.test.js
Expand Up @@ -135,6 +135,13 @@ function runTests(mode) {
'/_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',
},
])

const warnings = (await browser.log('browser'))
.map((log) => log.message)
.join('\n')
expect(warnings).not.toMatch(
/was detected as the Largest Contentful Paint/gm
)
} finally {
if (browser) {
await browser.close()
Expand Down Expand Up @@ -658,6 +665,35 @@ 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
try {
browser = await webdriver(appPort, '/priority-missing-warning')
// Wait for image to load:
await check(async () => {
const result = await browser.eval(
`document.getElementById('responsive').naturalWidth`
)
if (result < 1) {
throw new Error('Image not ready')
}
return 'done'
}, 'done')
await waitFor(1000)
const warnings = (await browser.log('browser'))
.map((log) => log.message)
.join('\n')
expect(await hasRedbox(browser)).toBe(false)
expect(warnings).toMatch(
/Image with src (.*)wide.png(.*) was detected as the Largest Contentful Paint/gm
)
} finally {
if (browser) {
await browser.close()
}
}
})
} else {
//server-only tests
it('should not create an image folder in server/chunks', async () => {
Expand Down

0 comments on commit d35b823

Please sign in to comment.