From f5b7baca53d0be346d58838ca402ec6cb544c4e1 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Mon, 25 Oct 2021 22:29:08 +0200 Subject: [PATCH 1/3] Add note about improved CSS parser for styled-jsx to upgrading guide (#30280) ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint` --- docs/upgrading.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/upgrading.md b/docs/upgrading.md index dd0a4bd8d8a4e17..ad40d71a1c12fbe 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -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 `` inside a `` instead of `
`. From 2fe5d011e624fdc9f623222a152f7b15d3fbe3c1 Mon Sep 17 00:00:00 2001 From: Kara Date: Mon, 25 Oct 2021 14:04:00 -0700 Subject: [PATCH 2/3] Add "priority" section to Image Optimization doc (#30218) This change addresses some feedback that some users don't realize that the `priority` attribute exists and is critical for managing LCP when using the next/image component. It adds a dedicated section to the Image Optimization doc that explains how and when to use the attribute. Fixes #29624 ## Documentation / Examples - [x] Make sure the linting passes by running `yarn lint` --- docs/basic-features/image-optimization.md | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/basic-features/image-optimization.md b/docs/basic-features/image-optimization.md index e45b212f5debb15..bd6267c378796be 100644 --- a/docs/basic-features/image-optimization.md +++ b/docs/basic-features/image-optimization.md @@ -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 ( + <> +

My Homepage

+ Picture of the author +

Welcome to my homepage!

+ + ) +} +``` + +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. From 4782cacecfa60fb755344f6f7e195152cb098a63 Mon Sep 17 00:00:00 2001 From: Steven Date: Mon, 25 Oct 2021 17:59:00 -0400 Subject: [PATCH 3/3] Add warning when LCP image is missing `priority` prop (#30221) --- docs/api-reference/next/image.md | 2 +- packages/next/client/image.tsx | 41 +++++++++++++++++++ .../default/pages/priority-missing-warning.js | 27 ++++++++++++ .../default/test/index.test.js | 36 ++++++++++++++++ 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 test/integration/image-component/default/pages/priority-missing-warning.js diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index cf7310e0e651f69..acd8eed4532cb89 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -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`. diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 9c4a2ae7a6d744c..83dbf6d474e98e2 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -10,6 +10,11 @@ import { import { useIntersection } from './use-intersection' const loadedImageURLs = new Set() +const allImgs = new Map< + string, + { src: string; priority: boolean; placeholder: string } +>() +let perfObserver: PerformanceObserver | undefined const emptyDataURL = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' @@ -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({ @@ -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 ( {hasSizer ? ( diff --git a/test/integration/image-component/default/pages/priority-missing-warning.js b/test/integration/image-component/default/pages/priority-missing-warning.js new file mode 100644 index 000000000000000..9ad9b0db730ce01 --- /dev/null +++ b/test/integration/image-component/default/pages/priority-missing-warning.js @@ -0,0 +1,27 @@ +import React from 'react' +import Image from 'next/image' + +const Page = () => { + return ( +
+

Priority Missing Warning Page

+ + + +
+ ) +} + +export default Page diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index c3c21dc5af424af..4998da538db1528 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -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() @@ -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 () => {