<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged speed</title>
<link>https://coderlegion.com/tag/speed</link>
<description>Powered by Question2Answer</description>
<item>
<title>How We Made a Photo-Heavy Site Load Fast: Image Optimization and Edge Caching That Actually Moved</title>
<link>https://coderlegion.com/22622/how-made-photo-heavy-site-load-fast-image-optimization-and-edge-caching-that-actually-moved</link>
<description>&lt;p&gt;If your site's main content &lt;em&gt;is&lt;/em&gt; images, performance is not a nice-to-have — it's the product. On a photography portfolio, every gallery is a wall of large files, and each one competes for bandwidth, blocks rendering, and drags down your Core Web Vitals.&lt;/p&gt;
&lt;p&gt;We ran into exactly this while building &lt;a rel=&quot;nofollow&quot; href=&quot;https://fotio.app&quot;&gt;fotio.app&lt;/a&gt;, a marketplace for booking professional photographers across 20+ cities in Spain. Portfolio pages routinely load dozens of high-resolution photos, and early on our Largest Contentful Paint (LCP) was embarrassing. This post walks through what we changed, what actually helped, and — importantly — the CDN and caching details that most &quot;just add a CDN&quot; articles skip.&lt;/p&gt;
&lt;h3 id=&quot;the-starting-point&quot;&gt;The starting point&lt;/h3&gt;
&lt;p&gt;A typical portfolio page looked like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;30–60 images per page, many well over 1 MB each&lt;/li&gt;
&lt;li&gt;Original JPEGs served at full resolution regardless of the viewport&lt;/li&gt;
&lt;li&gt;A single origin server, so a user in Mexico City pulled every byte across the Atlantic&lt;/li&gt;
&lt;li&gt;No lazy loading — everything fetched on page load, including images far below the fold&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result was a heavy page, a slow first paint, and layout that jumped around as images popped in. Let's fix it in layers.&lt;/p&gt;
&lt;h3 id=&quot;layer-1-serve-modern-formats&quot;&gt;Layer 1: Serve modern formats&lt;/h3&gt;
&lt;p&gt;The single biggest win was switching away from unoptimized JPEG. AVIF and WebP produce dramatically smaller files at equivalent visual quality — AVIF frequently lands 40–50% smaller than a comparable JPEG.&lt;/p&gt;
&lt;p&gt;The trick is to serve the best format the browser supports without forking your markup. The &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element handles this declaratively:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;picture&amp;gt;
  &amp;lt;source srcset=&quot;/img/session.avif&quot; type=&quot;image/avif&quot; /&amp;gt;
  &amp;lt;source srcset=&quot;/img/session.webp&quot; type=&quot;image/webp&quot; /&amp;gt;
  &amp;lt;img src=&quot;/img/session.jpg&quot; alt=&quot;Outdoor family session in Seville&quot; /&amp;gt;
&amp;lt;/picture&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The browser walks the sources top to bottom and uses the first format it understands, falling back to the plain JPEG for anything ancient.&lt;/p&gt;
&lt;h3 id=&quot;layer-2-stop-shipping-desktop-images-to-phones&quot;&gt;Layer 2: Stop shipping desktop images to phones&lt;/h3&gt;
&lt;p&gt;A 2400px-wide photo on a 390px phone screen is pure waste. Responsive &lt;code&gt;srcset&lt;/code&gt; lets the browser pick an appropriately sized file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;img
  src=&quot;/img/session-800.jpg&quot;
  srcset=&quot;/img/session-400.jpg 400w,
          /img/session-800.jpg 800w,
          /img/session-1600.jpg 1600w&quot;
  sizes=&quot;(max-width: 600px) 90vw, 800px&quot;
  alt=&quot;Couple love story session in Barcelona&quot;
  loading=&quot;lazy&quot;
  decoding=&quot;async&quot;
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two cheap wins hide in there: &lt;code&gt;loading=&quot;lazy&quot;&lt;/code&gt; defers off-screen images until the user scrolls near them, and &lt;code&gt;decoding=&quot;async&quot;&lt;/code&gt; keeps image decoding off the main thread.&lt;/p&gt;
&lt;h3 id=&quot;layer-3-reserve-space-so-the-layout-doesn-t-jump&quot;&gt;Layer 3: Reserve space so the layout doesn't jump&lt;/h3&gt;
&lt;p&gt;Lazy-loaded images that pop in without reserved space cause Cumulative Layout Shift (CLS) — content jumping around as photos arrive. Always give the browser the dimensions up front:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;img src=&quot;/img/session.jpg&quot; width=&quot;1600&quot; height=&quot;1067&quot; alt=&quot;...&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a nicer perceived load, we also render a tiny blurred placeholder (an LQIP — low-quality image placeholder) that's swapped for the real image once it arrives. It's a few hundred bytes of inlined base64 that makes the page feel instant even before the full photo lands.&lt;/p&gt;
&lt;h3 id=&quot;layer-4-cdn-and-edge-caching-the-part-people-gloss-over&quot;&gt;Layer 4: CDN and edge caching — the part people gloss over&lt;/h3&gt;
&lt;p&gt;Everything above shrinks the files. A CDN changes &lt;em&gt;where they come from&lt;/em&gt;, and this is where a photo-heavy site lives or dies.&lt;/p&gt;
&lt;h4 id=&quot;why-it-matters&quot;&gt;Why it matters&lt;/h4&gt;
&lt;p&gt;With a single origin, distance becomes latency. A user far from your server pays a round-trip penalty on every request, and images are your heaviest resource. A CDN copies your files to edge servers physically close to the user, so the bytes travel a short distance instead of across the world.&lt;/p&gt;
&lt;h4 id=&quot;how-the-cache-actually-behaves&quot;&gt;How the cache actually behaves&lt;/h4&gt;
&lt;p&gt;The first request for a file from a given region is a &lt;strong&gt;cache miss&lt;/strong&gt;: the CDN fetches it from your origin, stores a copy on the nearest edge node, and serves it. Every subsequent request from that region is a &lt;strong&gt;cache hit&lt;/strong&gt; — served straight from the edge, never touching your origin. Your job is to maximize hits and make each cached copy live as long as possible.&lt;/p&gt;
&lt;h4 id=&quot;cache-control-headers&quot;&gt;Cache-Control headers&lt;/h4&gt;
&lt;p&gt;For images that never change, tell browsers and the CDN to hold onto them essentially forever:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Cache-Control: public, max-age=31536000, immutable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's one year, and &lt;code&gt;immutable&lt;/code&gt; tells the browser not to even bother revalidating. HTML is the opposite — it changes, so it gets a short TTL or no caching at all. Applying one blanket rule to everything is a classic mistake; split your static assets from your dynamic documents.&lt;/p&gt;
&lt;h4 id=&quot;cache-busting-with-hashed-filenames&quot;&gt;Cache busting with hashed filenames&lt;/h4&gt;
&lt;p&gt;Once a file is cached &quot;forever,&quot; how do you ever update it? You don't change the content at the same URL — you change the URL. Embed a content hash in the filename:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;session.a3f9b2.avif   →   session.7c1e04.avif   (after re-export)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The old URL stays cached and harmless; the new one is a fresh miss that gets cached in turn. &lt;code&gt;immutable&lt;/code&gt; plus hashed filenames is the standard pairing, and it means you never fight a stale cache again.&lt;/p&gt;
&lt;h4 id=&quot;transform-at-the-edge&quot;&gt;Transform at the edge&lt;/h4&gt;
&lt;p&gt;Many CDNs (Cloudflare Images, Fastly, bunny.net, imgix) can convert and resize on the fly at the edge, reading the browser's &lt;code&gt;Accept&lt;/code&gt; header and returning AVIF or WebP from a single canonical URL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/cdn/session.jpg?format=auto&amp;amp;width=800
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This collapses the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;/&lt;code&gt;srcset&lt;/code&gt; gymnastics into one source of truth and offloads the work from your origin entirely.&lt;/p&gt;
&lt;h4 id=&quot;the-metric-this-moves&quot;&gt;The metric this moves&lt;/h4&gt;
&lt;p&gt;Edge caching lands squarely on &lt;strong&gt;TTFB&lt;/strong&gt; (time to first byte). A cache hit from a nearby edge answers in tens of milliseconds; a trip to a distant origin can be several hundred. Since your LCP element on a portfolio page is almost always an image, a faster TTFB on that image flows straight through to a faster LCP.&lt;/p&gt;
&lt;h3 id=&quot;what-the-numbers-looked-like&quot;&gt;What the numbers looked like&lt;/h3&gt;
&lt;p&gt;Rough before/after on a representative gallery page (measured with WebPageTest from a mid-tier connection):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Metric &lt;/th&gt;&lt;th&gt;Before &lt;/th&gt;&lt;th&gt;After&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Page weight &lt;/td&gt;&lt;td&gt;~9 MB &lt;/td&gt;&lt;td&gt;~1.8 MB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;LCP &lt;/td&gt;&lt;td&gt;~4.8 s &lt;/td&gt;&lt;td&gt;~1.6 s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TTFB (image, distant region) &lt;/td&gt;&lt;td&gt;~380 ms &lt;/td&gt;&lt;td&gt;~40 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CLS &lt;/td&gt;&lt;td&gt;0.21 &lt;/td&gt;&lt;td&gt;0.02&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Most of the weight reduction came from format + responsive sizing; most of the &lt;em&gt;latency&lt;/em&gt; reduction came from edge caching. They're complementary — you want both.&lt;/p&gt;
&lt;h3 id=&quot;takeaways&quot;&gt;Takeaways&lt;/h3&gt;
&lt;p&gt;If you're optimizing a photo-heavy site, work in this order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Modern formats&lt;/strong&gt; (AVIF/WebP with a JPEG fallback) — biggest byte savings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Responsive &lt;code&gt;srcset&lt;/code&gt; + lazy loading&lt;/strong&gt; — stop over-serving and over-fetching.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explicit dimensions + LQIP&lt;/strong&gt; — kill layout shift and improve perceived speed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CDN with aggressive, correct caching&lt;/strong&gt; — long &lt;code&gt;immutable&lt;/code&gt; TTLs, hashed filenames, edge transforms.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;None of these are exotic, but the caching layer is the one most teams under-configure, and it's the one that decides whether a user two continents away has a good time.&lt;/p&gt;
&lt;p&gt;You can see the end result on a genuinely image-dense site at &lt;a rel=&quot;nofollow&quot; href=&quot;https://fotio.app&quot;&gt;fotio.app&lt;/a&gt; — every city and portfolio page leans on exactly this pipeline.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22622/how-made-photo-heavy-site-load-fast-image-optimization-and-edge-caching-that-actually-moved</guid>
<pubDate>Tue, 14 Jul 2026 11:59:43 +0000</pubDate>
</item>
</channel>
</rss>