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

Update to stable: next/future/image, remotePatterns, unoptimized #40142

Merged
merged 7 commits into from Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
74 changes: 27 additions & 47 deletions docs/api-reference/next/future/image.md
@@ -1,35 +1,23 @@
---
description: Try the latest Image Optimization with the experimental `next/future/image` component.
description: Try the latest Image Optimization with the new `next/future/image` component.
---

# next/future/image

<details>
<summary><b>Version History</b></summary>

| Version | Changes |
| --------- | -------------------------------------------- |
| `v12.3.0` | Changed `alt` property to required. |
| `v12.2.4` | Support for `fill` property added. |
| `v12.2.0` | Experimental `next/future/image` introduced. |
| Version | Changes |
| --------- | --------------------------------------------------------------------------------------------------------------------------- |
| `v12.3.0` | `next/future/image` component stable. `remotePatterns` config stable. `unoptimized` config stable. `alt` property required. |
| `v12.2.4` | `fill` property added. |
| `v12.2.0` | Experimental `next/future/image` component introduced. |

</details>

The `next/future/image` component is an experiment to improve both the performance and developer experience of `next/image` by using the native `<img>` element with better default behavior.
The `next/future/image` component improves both the performance and developer experience of `next/image` by using the native `<img>` element with better default behavior.

This new component is considered experimental and therefore not covered by semver, and may cause unexpected or broken application behavior. This component uses browser native [lazy loading](https://caniuse.com/loading-lazy-attr), which may fallback to eager loading for older browsers before Safari 15.4. When using the blur-up placeholder, older browsers before Safari 12 will fallback to empty placeholder. When using styles with `width`/`height` of `auto`, it is possible to cause [Layout Shift](https://web.dev/cls/) on older browsers before Safari 15 that don't [preserve the aspect ratio](https://caniuse.com/mdn-html_elements_img_aspect_ratio_computed_from_attributes). For more details, see [this MDN video](https://www.youtube.com/watch?v=4-d_SoCHeWE).

To use `next/future/image`, add the following to your `next.config.js` file:

```js
module.exports = {
experimental: {
images: {
allowFutureImage: true,
},
},
}
```
This component uses browser native [lazy loading](https://caniuse.com/loading-lazy-attr), which may fallback to eager loading for older browsers before Safari 15.4. When using the blur-up placeholder, older browsers before Safari 12 will fallback to empty placeholder. When using styles with `width`/`height` of `auto`, it is possible to cause [Layout Shift](https://web.dev/cls/) on older browsers before Safari 15 that don't [preserve the aspect ratio](https://caniuse.com/mdn-html_elements_img_aspect_ratio_computed_from_attributes). For more details, see [this MDN video](https://www.youtube.com/watch?v=4-d_SoCHeWE).

## Comparison

Expand Down Expand Up @@ -374,14 +362,12 @@ You can also [generate a solid color Data URL](https://png-pixel.com) to match t
When true, the source image will be served as-is instead of changing quality,
size, or format. Defaults to `false`.

This prop can be assigned to all images by updating `next.config.js` with the following experimental configuration:
This prop can be assigned to all images by updating `next.config.js` with the following configuration:

```js
module.exports = {
experimental: {
images: {
unoptimized: true,
},
images: {
unoptimized: true,
},
}
```
Expand All @@ -399,23 +385,19 @@ Other properties on the `<Image />` component will be passed to the underlying

### Remote Patterns

> Note: The `remotePatterns` configuration is currently **experimental** and subject to change. Please use [`domains`](#domains) for production use cases.

To protect your application from malicious users, configuration is required in order to use external images. This ensures that only external images from your account can be served from the Next.js Image Optimization API. These external images can be configured with the `remotePatterns` property in your `next.config.js` file, as shown below:

```js
module.exports = {
experimental: {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
port: '',
pathname: '/account123/**',
},
],
},
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
port: '',
pathname: '/account123/**',
},
],
},
}
```
Expand All @@ -426,15 +408,13 @@ Below is another example of the `remotePatterns` property in the `next.config.js

```js
module.exports = {
experimental: {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**.example.com',
},
],
},
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**.example.com',
},
],
},
}
```
Expand Down
49 changes: 20 additions & 29 deletions docs/api-reference/next/image.md
Expand Up @@ -16,6 +16,7 @@ description: Enable Image Optimization with the built-in Image component.

| Version | Changes |
| --------- | --------------------------------------------------------------------------------------------------------- |
| `v12.3.0` | `remotePatterns` and `unoptimized` configuration is stable. |
| `v12.2.0` | Experimental `remotePatterns` and experimental `unoptimized` configuration added. `layout="raw"` removed. |
| `v12.1.1` | `style` prop added. Experimental[\*](#experimental-raw-layout-mode) support for `layout="raw"` added. |
| `v12.1.0` | `dangerouslyAllowSVG` and `contentSecurityPolicy` configuration added. |
Expand Down Expand Up @@ -93,8 +94,6 @@ The layout behavior of the image as the viewport changes size.
- When `fill`, the image will stretch both width and height to the dimensions of the parent element, provided the parent element is relative.
- This is usually paired with the [`objectFit`](#objectFit) property.
- Ensure the parent element has `position: relative` in their stylesheet.
- When `raw`[\*](#experimental-raw-layout-mode), the image will be rendered as a single image element with no wrappers, sizers or other responsive behavior.
- If your image styling will change the size of a `raw` image, you should include the `sizes` property for proper image serving. Otherwise your image will be requested as though it has fixed width and height.
- [Demo background image](https://image-component.nextjs.gallery/background)

### loader
Expand Down Expand Up @@ -324,14 +323,12 @@ const Example = () => {
When true, the source image will be served as-is instead of changing quality,
size, or format. Defaults to `false`.

This prop can be assigned to all images by updating `next.config.js` with the following experimental configuration:
This prop can be assigned to all images by updating `next.config.js` with the following configuration:

```js
module.exports = {
experimental: {
images: {
unoptimized: true,
},
images: {
unoptimized: true,
},
}
```
Expand All @@ -351,23 +348,19 @@ Other properties on the `<Image />` component will be passed to the underlying

### Remote Patterns

> Note: The `remotePatterns` configuration is currently **experimental** and subject to change. Please use [`domains`](#domains) for production use cases.

To protect your application from malicious users, configuration is required in order to use external images. This ensures that only external images from your account can be served from the Next.js Image Optimization API. These external images can be configured with the `remotePatterns` property in your `next.config.js` file, as shown below:

```js
module.exports = {
experimental: {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
port: '',
pathname: '/account123/**',
},
],
},
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
port: '',
pathname: '/account123/**',
},
],
},
}
```
Expand All @@ -378,15 +371,13 @@ Below is another example of the `remotePatterns` property in the `next.config.js

```js
module.exports = {
experimental: {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**.example.com',
},
],
},
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**.example.com',
},
],
},
}
```
Expand Down
2 changes: 1 addition & 1 deletion docs/manifest.json
Expand Up @@ -427,7 +427,7 @@
}
},
{
"title": "next/future/image (experimental)",
"title": "next/future/image",
ijjk marked this conversation as resolved.
Show resolved Hide resolved
"path": "/docs/api-reference/next/future/image.md"
},
{
Expand Down
15 changes: 4 additions & 11 deletions errors/invalid-images-config.md
Expand Up @@ -31,17 +31,10 @@ module.exports = {
dangerouslyAllowSVG: false,
// set the Content-Security-Policy header
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
// the following are experimental features, and may cause breaking changes
},
experimental: {
images: {
// limit of 50 objects
remotePatterns: [],
// when true, every image will be unoptimized
unoptimized: false,
// when true, allow `next/future/image` to be imported
allowFutureImage: false,
},
// limit of 50 objects
remotePatterns: [],
// when true, every image will be unoptimized
unoptimized: false,
},
}
```
Expand Down
10 changes: 4 additions & 6 deletions examples/cms-sanity/next.config.js
@@ -1,11 +1,9 @@
/** @type {import('next').NextConfig} */
module.exports = {
experimental: {
images: {
allowFutureImage: true,
},
},
images: {
domains: ['cdn.sanity.io', 'source.unsplash.com'],
remotePatterns: [
{ hostname: 'cdn.sanity.io' },
{ hostname: 'source.unsplash.com' },
],
},
}
2 changes: 1 addition & 1 deletion packages/next/build/index.ts
Expand Up @@ -2405,7 +2405,7 @@ export default async function build(
const { deviceSizes, imageSizes } = images
;(images as any).sizes = [...deviceSizes, ...imageSizes]
;(images as any).remotePatterns = (
config?.experimental?.images?.remotePatterns || []
config?.images?.remotePatterns || []
).map((p: RemotePattern) => ({
// Should be the same as matchRemotePattern()
protocol: p.protocol,
Expand Down
6 changes: 2 additions & 4 deletions packages/next/build/webpack-config.ts
Expand Up @@ -196,14 +196,12 @@ export function getDefineEnv({
path: config.images.path,
loader: config.images.loader,
dangerouslyAllowSVG: config.images.dangerouslyAllowSVG,
experimentalUnoptimized: config?.experimental?.images?.unoptimized,
experimentalFuture: config.experimental?.images?.allowFutureImage,
unoptimized: config?.images?.unoptimized,
...(dev
? {
// pass domains in development to allow validating on the client
domains: config.images.domains,
experimentalRemotePatterns:
config.experimental?.images?.remotePatterns,
remotePatterns: config.images?.remotePatterns,
}
: {}),
}),
Expand Down
19 changes: 3 additions & 16 deletions packages/next/client/future/image.tsx
Expand Up @@ -15,11 +15,6 @@ import {
import { ImageConfigContext } from '../../shared/lib/image-config-context'
import { warnOnce } from '../../shared/lib/utils'

const {
experimentalFuture = false,
experimentalRemotePatterns = [],
experimentalUnoptimized,
} = (process.env.__NEXT_IMAGE_OPTS as any) || {}
const configEnv = process.env.__NEXT_IMAGE_OPTS as any as ImageConfigComplete
const allImgs = new Map<
string,
Expand Down Expand Up @@ -475,10 +470,7 @@ function defaultLoader({
)
}

if (
!src.startsWith('/') &&
(config.domains || experimentalRemotePatterns)
) {
if (!src.startsWith('/') && (config.domains || config.remotePatterns)) {
let parsedSrc: URL
try {
parsedSrc = new URL(src)
Expand All @@ -492,7 +484,7 @@ function defaultLoader({
if (process.env.NODE_ENV !== 'test') {
// We use dynamic require because this should only error in development
const { hasMatch } = require('../../shared/lib/match-remote-pattern')
if (!hasMatch(config.domains, experimentalRemotePatterns, parsedSrc)) {
if (!hasMatch(config.domains, config.remotePatterns, parsedSrc)) {
throw new Error(
`Invalid src prop (${src}) on \`next/image\`, hostname "${parsedSrc.hostname}" is not configured under images in your \`next.config.js\`\n` +
`See more info: https://nextjs.org/docs/messages/next-image-unconfigured-host`
Expand Down Expand Up @@ -530,11 +522,6 @@ export default function Image({
blurDataURL,
...all
}: ImageProps) {
if (!experimentalFuture && process.env.NODE_ENV !== 'test') {
throw new Error(
`The "next/future/image" component is experimental and may be subject to breaking changes. To enable this experiment, please include \`experimental: { images: { allowFutureImage: true } }\` in your next.config.js file.`
)
}
const configContext = useContext(ImageConfigContext)
const config: ImageConfig = useMemo(() => {
const c = configEnv || configContext || imageConfigDefault
Expand Down Expand Up @@ -600,7 +587,7 @@ export default function Image({
unoptimized = true
isLazy = false
}
if (experimentalUnoptimized) {
if (config.unoptimized) {
unoptimized = true
}

Expand Down
11 changes: 3 additions & 8 deletions packages/next/client/image.tsx
Expand Up @@ -22,8 +22,6 @@ function normalizeSrc(src: string): string {
return src[0] === '/' ? src.slice(1) : src
}

const { experimentalRemotePatterns = [], experimentalUnoptimized } =
(process.env.__NEXT_IMAGE_OPTS as any) || {}
const configEnv = process.env.__NEXT_IMAGE_OPTS as any as ImageConfigComplete
const loadedImageURLs = new Set<string>()
const allImgs = new Map<
Expand Down Expand Up @@ -137,10 +135,7 @@ function defaultLoader({
)
}

if (
!src.startsWith('/') &&
(config.domains || experimentalRemotePatterns)
) {
if (!src.startsWith('/') && (config.domains || config.remotePatterns)) {
let parsedSrc: URL
try {
parsedSrc = new URL(src)
Expand All @@ -154,7 +149,7 @@ function defaultLoader({
if (process.env.NODE_ENV !== 'test') {
// We use dynamic require because this should only error in development
const { hasMatch } = require('../shared/lib/match-remote-pattern')
if (!hasMatch(config.domains, experimentalRemotePatterns, parsedSrc)) {
if (!hasMatch(config.domains, config.remotePatterns, parsedSrc)) {
throw new Error(
`Invalid src prop (${src}) on \`next/image\`, hostname "${parsedSrc.hostname}" is not configured under images in your \`next.config.js\`\n` +
`See more info: https://nextjs.org/docs/messages/next-image-unconfigured-host`
Expand Down Expand Up @@ -673,7 +668,7 @@ export default function Image({
if (typeof window !== 'undefined' && loadedImageURLs.has(src)) {
isLazy = false
}
if (experimentalUnoptimized) {
if (config.unoptimized) {
unoptimized = true
}

Expand Down
5 changes: 2 additions & 3 deletions packages/next/export/index.ts
Expand Up @@ -321,8 +321,7 @@ export default async function exportApp(

const {
i18n,
images: { loader = 'default' },
experimental,
images: { loader = 'default', unoptimized },
} = nextConfig

if (i18n && !options.buildExport) {
Expand All @@ -344,7 +343,7 @@ export default async function exportApp(
if (
isNextImageImported &&
loader === 'default' &&
!experimental?.images?.unoptimized &&
!unoptimized &&
!hasNextSupport
) {
throw new Error(
Expand Down