Behind the scenes: how Heart Photos gets an image to your screen

· He.art

A Viking longship with a red and cream striped sail crossing a misty fjord between snowy mountains
A Viking longship with a red and cream striped sail crossing a misty fjord between snowy mountains · Made by AI

When you open a gallery in Heart Photos, dozens of images arrive at once, and they should arrive fast whether you are on a phone or a 4K screen. Today we made a change to how long those images are kept close to you, so this seems a good moment to show how the whole trip works, from the file we store to the image on your screen.

One address for every image

Every image in Heart Photos has a seven-character id, and one public address built from it: i.heart.photos/<id>. The lead image of this post, for example, lives at i.heart.photos/1ctio5s.

Add a width to the end and you get that image at that size: /w320, /w640, /w1280, /w1920, /w2560 or /w3840, the number being its width in pixels. There are a few named sizes too (/thumb, /mobile, /1080p, /4k), and /auto, which picks a size for you.

The address never changes, even when the image behind it does. If a photo is turned or re-rendered, or its thumbnail is changed, the same address now serves the new image. That keeps every link, every embed in a He.art doc and every page on our other sites working, and it is also the reason for the most careful part of this story, which comes further down.

The images themselves are AVIF files, a modern format that keeps a photograph sharp at a fraction of a JPEG's size.

The right size for your screen

A phone does not need a 4K image, and a large monitor looks soft with a small one. The /auto address chooses using client hints: small headers in which a browser can say how wide its window is and how dense its pixels are. Heart Photos multiplies the two and serves 1,280 pixels for a screen up to that width, 1,920 for anything up to 1,920, and 3,840 above that. A browser that sends no hints gets 1,280 on a phone and 1,920 elsewhere.

Widths made when they are first asked for

Making all six widths of every image ahead of time would mean encoding many files that nobody ever looks at. So since Oct 16, 2026, a new image has only its two most asked-for widths made straight away, 1,280 and 1,920 pixels, the sizes a phone and a desktop usually want.

Every other width is made the first time somebody asks for it. That first visitor is not kept waiting: they get the next wider file at once, and an encode for the width they asked for joins the queue on our image worker. The stand-in is marked to be kept for only a minute, so once the real file exists, the next visitor gets it. We never stand in a narrower file, because an image stretched up looks broken where a larger one only takes a moment longer.

A width as wide as the image itself, or wider, is never made at all: the full-size image is already the best answer, so that is what is served, and it is kept like any finished file.

Thirty days at the edge, starting today

Between our server and you sits Cloudflare's network, which keeps copies of our files in data centers around the world, so an image that is already there comes from nearby instead of from our server. How long it may keep a copy is set by the Cache-Control header: s-maxage is for shared caches like Cloudflare's, and max-age is for your own browser.

Until today, both were one hour. That meant an image nobody had opened for a while had dropped out of the edge, and the next visitor waited about a second for it to come all the way from our server. In a gallery of thirty-five thumbnails on one of our own sites, you could see them appear late.

So as of Nov 2, 2026, Cloudflare keeps a finished image for thirty days. Your browser still keeps it for one hour, and a stand-in for a minute.

Why we can keep it that long safely

Because an address can start serving a new image, a long cache is only safe if the edge forgets the old one the moment anything changes. Heart Photos tells it to. Whenever an image is re-rendered, made private or moved to the Trash, our server asks Cloudflare to forget every address that image is served at, every width and named size included. When you re-render an image yourself, that request goes out before your page is told the change is done, so the next thing you see is the new image.

With a thirty-day cache, a forget request that went astray would leave an old image out there for a month instead of an hour. So since today, each request is tried three times before we give up and log it.

Your browser's one-hour copy cannot be reached that way, which is why it stays short. When it does ask again, it sends the ETag it was given, a short fingerprint of the file, and if nothing has changed it gets a tiny "not modified" answer instead of the whole image again.

Watch it happen

You can see all of this in your own browser:

  1. Open any image on Heart Photos, right-click it and open the image in a new tab.
  2. Open your browser's developer tools (F12 in Chrome and Edge), choose the Network panel and reload.
  3. Click the image's request and look at its response headers. X-Heart-Variant names the file you were actually sent, Cache-Control says how long it may be kept, and Cloudflare's cf-cache-status says HIT when it came from the edge and MISS when it came from us.

Then change the width at the end of the address to one nobody has asked for yet. When we tried /w640 on this post's lead image, X-Heart-Variant said w1280 and Cache-Control said max-age=60: the wider stand-in, kept for a minute, while the real 640-pixel file was made. A few minutes later the same address answered with w640 itself, an 18 KB file in place of the 46 KB stand-in, kept for thirty days.

Try it

Every image you add to your library gets all of this without you doing a thing. Open He.art, add a few images to Heart Photos and share a gallery with a friend: by the time they open it, the sizes their screen needs will be on their way.

More from the blog