diff --git a/README.md b/README.md index 6770a8bb7c..6dda912d59 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,14 @@ Similarly, you can customize your publish directory in your `netlify.toml` file: Read more about [Netlify's build settings](https://docs.netlify.com/configure-builds/get-started/#basic-build-settings) in our docs. +## Image handling + +The plugin includes a function to generate images for `next/image`. The images are resized on the fly, so the first request will have a short delay. However because the function uses [On-Demand Builders](https://docs.netlify.com/configure-builds/on-demand-builders/), any subsequent requests for that image are served from the edge cache and are super-fast. + +By default, images are returned in the same format as the source image if they are in JPEG, PNG, WebP or AVIF format. If you are only targeting modern browsers and want to live life on the edge, you can [set the environment variable](https://docs.netlify.com/configure-builds/environment-variables/) `FORCE_WEBP_OUTPUT` to `"true"`, and it will return all images in WebP format. This will often lead to significant improvements in file size. However you should not use this if you need to support older browsers, as `next/image` does not support picture tag source fallback and images will appear broken. Check [browser support](https://caniuse.com/webp) to see if you are happy to do this. + +If you want to use remote images in `next/image`, you will need to add the image domains to an allow list. Setting them in `images.domains` in `next.config.js` is not supported: instead you should set the environment variable `NEXT_IMAGE_ALLOWED_DOMAINS` to a comma-separated list of domains, e.g. `NEXT_IMAGE_ALLOWED_DOMAINS="placekitten.com,unsplash.com"`. + ## Custom Netlify Redirects You can define custom redirects in a `_redirects` file. diff --git a/index.js b/index.js index 6c52b2bcd2..8039d44938 100644 --- a/index.js +++ b/index.js @@ -46,6 +46,14 @@ module.exports = { // Because we memoize nextConfig, we need to do this after the write file const nextConfig = await getNextConfig(utils.failBuild) + + if (nextConfig.images.domains.length !== 0 && !process.env.NEXT_IMAGE_ALLOWED_DOMAINS) { + console.log( + `Image domains set in next.config.js are ignored.\nPlease set the env variable NEXT_IMAGE_ALLOWED_DOMAINS to "${nextConfig.images.domains.join( + ',', + )}" instead`, + ) + } await restoreCache({ cache: utils.cache, distDir: nextConfig.distDir }) }, async onBuild({ diff --git a/src/lib/templates/imageFunction.js b/src/lib/templates/imageFunction.js index 8b56362298..66bad01a65 100644 --- a/src/lib/templates/imageFunction.js +++ b/src/lib/templates/imageFunction.js @@ -34,9 +34,24 @@ const handler = async (event) => { const quality = parseInt(q) || 60 - const imageUrl = parsedUrl.startsWith('/') - ? `${process.env.DEPLOY_URL || `http://${event.headers.host}`}${parsedUrl}` - : parsedUrl + let imageUrl + // Relative image + if (parsedUrl.startsWith('/')) { + imageUrl = `${process.env.DEPLOY_URL || `http://${event.headers.host}`}${parsedUrl}` + } else { + // Remote images need to be in the allowlist + const allowedDomains = process.env.NEXT_IMAGE_ALLOWED_DOMAINS + ? process.env.NEXT_IMAGE_ALLOWED_DOMAINS.split(',').map((domain) => domain.trim()) + : [] + + if (!allowedDomains.includes(new URL(parsedUrl).hostname)) { + return { + statusCode: 403, + body: 'Image is not from a permitted domain', + } + } + imageUrl = parsedUrl + } const imageData = await fetch(imageUrl) @@ -68,7 +83,7 @@ const handler = async (event) => { } } - if (process.env.FORCE_WEBP_OUTPUT) { + if (process.env.FORCE_WEBP_OUTPUT === 'true' || process.env.FORCE_WEBP_OUTPUT === '1') { ext = 'webp' }