How to resize and optimize images with Netlify Image CDN

Request /.netlify/images with query parameters to resize, crop, and reformat on demand. The parameters, remote-domain allowlisting, and why fm=blurhash renders nothing.

Tutorial

Key takeaways

Request /.netlify/images?url=/your-image.jpg with query parameters and Netlify transforms the image at the edge. Nothing to install, no function to write, and url is the only required parameter. If your framework has an image component, use it: Angular, Astro, Nuxt, Next.js, and Gatsby all wire up to Image CDN automatically.

  • A bare url still reformats. No size or format parameters means the image keeps its dimensions but converts to avif or webp based on the browser’s Accept header.
  • fit=cover requires both w and h. Supplying only one silently misbehaves.
  • Remote images need allowlisting in netlify.toml under [images] remote_images, and the URL needs encodeURIComponent.
  • fm=blurhash returns a text string, not image bytes. Pointing <img src> at it renders nothing.
  • A local 404 usually means you’re running the wrong dev server. The endpoint only exists under netlify dev.
  • Split Testing is not supported, and results can be inconsistent between split-test branches.

Transformation without a pipeline

Image optimization used to mean a build step, a plugin, and a set of pregenerated sizes you had to guess at in advance. Image CDN replaces that with a URL. You ask for the image at the size and format you want, and it’s transformed at the edge and cached.

The parameters are few enough to learn in a minute. The parts worth reading carefully are the fit behaviors, which decide whether you get exact dimensions or a preserved aspect ratio, and the remote-image setup, which has two easy-to-miss requirements.

# resize + crop to a 50px square, retain left side, convert to webp at q=80
curl -vs 'https://mysitename.netlify.app/.netlify/images?url=/owl.jpeg&fit=cover&w=50&h=50&position=left&fm=webp&q=80'

When to use the endpoint, and when to use your framework

Use your framework’s native image component where one exists. It wires to Image CDN automatically and handles responsive srcset generation for you. Hand-building URLs is for cases the component doesn’t cover: a transformation in a redirect rule, an image in an email template, a thumbnail generated inside a function.

There’s no legacy or deprecated form of this. /.netlify/images is the only programmatic surface.

Two places it won’t go: Split Testing is not supported, and Image CDN is not currently part of Netlify’s HIPAA-compliant hosting offering.

Worked example

Query parameters

GET /.netlify/images?url=<source>&...

ParamValuesNotes
urlRelative path or full remote URLRequired. The only required param.
wInteger pxWidth
hInteger pxHeight
fitcontain (default), cover, fillResize behavior
positioncenter (default), top, bottom, left, rightOnly applies when fit=cover
fmavif, jpg, png, webp, gif, blurhashOutput format. webp and gif can be animated.
qInteger 1–100 (default 75)Only for avif, jpg, gif, webp

How fit behaves

fit=Aspect ratio keptCrops excessReturns exact dimensions
containYesNoNo, one dimension may be smaller
coverNoYesYes, scaled proportionally then cropped
fillNoNoYes, stretched or squished if needed

fit=cover requires both w and h. Supplying only one silently misbehaves rather than erroring, which makes it an annoying bug to chase. With contain, giving one dimension calculates the other to preserve the aspect ratio.

Format negotiation

If you pass just url with no size or format, the image keeps its size and shape but is still reformatted to avif or webp based on the browser’s Accept header. That alone is often worth doing.

With no fm specified, you get webp if the browser accepts it, else avif if accepted, else the original format.

Remote source images

Two requirements. First, allowlist the domain in netlify.toml:

[images]
remote_images = ["https://my-images.com/.*", "https://animals.more-images.com/[bcr]at/.*"]

Second, percent-encode the remote URL:

const src = `/.netlify/images?url=${encodeURIComponent("https://my-images.com/owl.jpeg")}`;

Always encodeURIComponent the remote URL, because a URL containing ? or & breaks otherwise.

On the pattern syntax: escape only the dot. Forward slashes are not regex metacharacters, so don’t write https:\/\/. In netlify.toml, use a single-quoted literal string like 'https://example\.com/.*', or double the backslash in a double-quoted string ("https://example\\.com/.*"). A bare \. inside double quotes is invalid TOML.

Remote sources must be publicly accessible. Netlify does not forward Authorization or Cookie headers to remote sources. For images that need auth, use self-authorizing URLs like S3 presigned URLs, and make sure your remote_images pattern matches them.

Reusable transformations via redirects

To reuse the same parameters across many images, put them in a redirect.

_redirects:

/transform-small/* /.netlify/images?url=/:splat&w=50&h=50 200

netlify.toml:

[[redirects]]
from = "/transform-small/*"
to = "/.netlify/images?url=/:splat&w=50&h=50"
status = 200

Then GET /transform-small/owl.jpeg gives you a 50×50 transform. Avoid cross-site redirects for transformations, which hurt performance.

Caching headers

_headers:

/source-images/*
Cache-Control: public, max-age=604800, must-revalidate

Headers set on a source image apply to the transformed asset that Image CDN serves. Two limits worth knowing: custom headers cannot be applied to remote source images (Netlify respects whatever the external domain sends), and Cache-Control on source images applies only to browsers and CDNs in front of Netlify, not to the Netlify cache itself. For that, see How to control CDN caching on Netlify.

Framework integrations

FrameworkPrerequisiteRemote allowlist
AngularNone. NgOptimizedImage uses it automatically[images] remote_images in netlify.toml
AstroNone. <Image /> uses it automaticallyimage.domains / image.remotePatterns in astro.config.mjs
NuxtNone. nuxt/image uses it automaticallyimage.domains in nuxt.config.ts
Next.jsNext 13.5+ and adapter v5remotePatterns in next.config.js
GatsbyEnv NETLIFY_IMAGE_CDN=true plus a Contentful, Drupal, or WordPress source plugin[images] remote_images in netlify.toml

Note the allowlist lives in a different place per framework. Setting remote_images in netlify.toml won’t help an Astro <Image /> call; that needs image.domains in the Astro config.

Common failure modes

You get a 404 on /.netlify/images locally. Almost always because a framework dev server (vite, next dev, astro dev) is running instead of netlify dev. The endpoint, [images] allowlisting, and image redirects only exist under netlify dev. Your URL is probably fine.

fm=blurhash renders nothing. It returns a BlurHash text string, not image bytes. Pointing an <img src> or a CSS background at it produces an empty box. Fetch the string server-side or ahead of time, decode it client-side with a BlurHash library (blurha.sh), then load the real image as a separate request without fm=blurhash.

Your fit=cover crop is wrong. Check that you passed both w and h. With only one, it misbehaves silently.

Your remote image 404s or won’t load. Three things to check: is the domain in remote_images, did you encodeURIComponent the URL, and is the source publicly accessible? Netlify won’t forward Authorization or Cookie headers.

Your remote_images pattern doesn’t match. Look for over-escaping. Forward slashes aren’t metacharacters, so https:\/\/ is wrong. And in a double-quoted TOML string, a bare \. is invalid; use a single-quoted string instead.

You get a 404 on a transformation you think is valid. Invalid transformation parameter values return 404. Check your fit, fm, and q values against the allowed sets.

Your custom cache headers aren’t applying. They can’t be applied to remote source images. And Cache-Control on a source image reaches browsers and upstream CDNs, not the Netlify cache.

Your image results differ between split-test branches. Split Testing isn’t supported with Image CDN.

You changed a source image and the old one is still served. It shouldn’t be. Atomic deploys are respected, so changing a source image in a new deploy re-runs transformations on new requests.

Reference

Response codes

CodeMeaning
404Invalid transformation parameter values
200Valid, new transformation. Returns content plus content-type.
304Previously transformed

Format selection order

With no fm parameter: webp if the browser accepts it, else avif if accepted, else the original.

Caching and deploys

Transformed results are uniquely cached on Netlify’s edge. Atomic deploys are respected: changing a source image in a new deploy re-runs transformations on new requests, so stale assets aren’t served.

Local development

Run netlify dev, which mimics production including Image CDN. A framework dev server does not provide the endpoint.

User-uploaded image pipelines

For a full user-upload pipeline composing Functions, Blobs, and Image CDN together, the source skill carries a dedicated guide. The shape is: a function receives the upload, stores the bytes in Netlify Blobs, and serves them back through /.netlify/images for transformation. See How to store files and objects with Netlify Blobs for the storage half.

Limitations

  • Split Testing is not supported; image results may be inconsistent between split-test branches.
  • Not currently supported in Netlify’s HIPAA-compliant hosting offering.

FAQs

How do I optimize images on Netlify? Request /.netlify/images?url=/your-image.jpg with parameters like w, h, fit, fm, and q. Or use your framework’s image component, which wires to Image CDN automatically. Even a bare url with no other parameters converts the image to webp or avif based on the browser.

How do I resize an image on the fly? Add w and h to the /.netlify/images request. Use fit=cover with both dimensions to crop to exact size, or fit=contain to preserve the aspect ratio.

How do I serve images from an external domain? Allowlist the domain in netlify.toml under [images] remote_images as a regex, then pass the URL through encodeURIComponent in the url parameter. The source has to be publicly accessible.

Why does my fit=cover crop look wrong? fit=cover requires both w and h. With only one it silently misbehaves.

Why is my blurhash placeholder blank? fm=blurhash returns a text string, not an image. Fetch it ahead of time, decode it client-side with a BlurHash library, then request the real image separately without fm=blurhash.

Why do I get a 404 on /.netlify/images locally? You’re probably running vite, next dev, or astro dev instead of netlify dev. The endpoint only exists under netlify dev.

Can I apply the same transformation to many images? Yes. Put the parameters in a redirect rule, so /transform-small/* rewrites to /.netlify/images?url=/:splat&w=50&h=50 with status 200.

What image formats can Netlify Image CDN output? avif, jpg, png, webp, gif, and blurhash (a text string). webp and gif can be animated.

Does Image CDN work with Next.js next/image? Yes, on Next 13.5+ with adapter v5. Set your remote domains in remotePatterns in next.config.js.

This article is generated from Netlify’s open-source agent guidance at netlify/context-and-tools, the same reference our AI coding agents use.


Got images to serve? Start at netlify.new.