Why Image Weight Mattered So Much
WaterBear’s entire experience is image-driven: video posters, category banners, partner logos, and campaign assets dominate every screen. That visual identity comes at a cost. When my team measured a typical video category page, we found the first load served 14 images, and those images accounted for roughly 85% of total page weight. That kind of ratio demands a deliberate optimization strategy.
We knew early on that we needed to apply every practical image optimization technique available. The challenge was balancing the immersive, high-production look of the platform with reasonable load times — a tradeoff every front-end team faces when imagery is central to the product.
Letting the CDN Do the Heavy Lifting
Sanity CMS gives us a CDN out of the box that handles both image optimization and caching. The WaterBear team uploads unoptimized, high-quality source images; the CDN then compresses them to the smallest reasonable size and caches the result so repeat views don't trigger another full download.
Requesting an optimized version is as simple as appending query parameters to the image URL:
https://cdn.sanity.io/.../image.jpg?w=1280&q=70&auto=format
w— sets image width;1280pxin this example.q— sets compression quality; we settled on 70% as the balance between visual fidelity and file size.format— set toauto, letting Sanity negotiate the best format based on the user's browser.
That single URL turns an unoptimized file into a fully optimized one. In most cases the CDN returns WebP, which offers significant file-size savings. An original 1.2 MB image drops to roughly 146 KB after optimization — and in our tests, all 14 image requests on a category page were individually smaller than that single unoptimized source file.
WebP enjoys broad browser support, but we still wanted a fallback for older browsers. Sanity handles that automatically too: it detects browser capabilities and serves the optimized JPG only when WebP isn't available. That's one decision we don't have to make ourselves.
AVIF would have been our preference — it promises even greater savings than WebP — but Sanity didn't support it at the time of development. There's a long-running GitHub ticket requesting the feature, and we're hopeful it lands eventually. Had we known about this limitation upfront, we might have weighed alternatives like Cloudinary more seriously. In hindsight, Sanity's tightly coupled CDN integration was too valuable to give up, but it's exactly the kind of consideration I'd research earlier on a future project.
Prioritizing the First Paint
Largest Contentful Paint (LCP) is the first meaningful visual a user gets, and for WaterBear, images are almost always part of that calculation. Optimizing the banner images at the top of the page gives us the fastest possible first impression while everything below the fold waits its turn.
Two modern attributes helped us control loading behavior. On hero images we used an eager loading strategy combined with a high fetchpriority value — clear signals to the browser that this image matters early in the process. We also added preload hints in the document <head> using Next.js image preload options, again flagged with high priority.
<!-- Above-the-fold Large Contentful Paint image -->
<img
fetchpriority="high"
alt="..."
src="..."
width="1280"
height="720"
class="..."
/>
Below-the-fold images get the opposite treatment. The loading attribute, now native in HTML, tells the browser to defer those requests until the images approach the viewport.
<!-- Below-the-fold, low-priority image -->
<img
src="..."
alt="..."
width="250"
height="350"
/>
This combination of strategies produced a measurable result: on our image-heavy video category pages, we cut image download size and the number of image requests by nearly 80% on first load. The page still grows as the user scrolls, but that weight is only added when it actually reaches the viewport.
Next Up: Responsive Source Selection
We're pleased with the savings so far, but there's more to extract. The next task on our roadmap is implementing srcset on images across the platform. It's a well-established technique in responsive design — it lets the browser pick from multiple image versions based on viewport width — but we deliberately deferred it because the earlier strategies offered the biggest return for the least effort.
Implementing srcset properly requires a degree of art direction. An image's composition at one screen size might be completely wrong at another, and the markup itself gets harder to read. The benefit is that high-resolution images never reach small screens.
Our planned markup specifies three image variants — 568px, 768px, and 1280px — and uses the sizes attribute to guide the browser. Above a 1024px viewport, it loads the largest variant; below that, it picks the most appropriate of the three based on the full viewport width (100vw). Crucially, the browser downloads only the selected version, never all three.
<img
width="1280"
height="720"
src="https://cdn.sanity.io/.../image.jpg?w=1280&..."
/>
Query Caching With TanStack Query
Sanity powers most of WaterBear’s content—video categories, archives, individual video pages, partner pages, and campaign landing pages. Users navigate between these frequently, often returning to the same category or landing page, which made the app a natural fit for query caching. To avoid repeating identical requests to the CMS, the team integrated TanStack Query (formerly react-query) for both data fetching and cache management.
const { isLoading, error, data } = useQuery( /* Options */ )
TanStack Query stores cached responses under the query key assigned to each request. A query key is an array: the first element names the query, and the second element is an object holding all values the query depends on—pagination, filters, query variables, and so on. For example, fetching a list of videos by category URL slug while filtering by duration would use a key similar to this:
const { isLoading, error, data } = useQuery(
{
queryKey: [
'video-category-list',
{ slug: categorySlug, filterBy: activeFilter }
],
queryFn: () => /* ... */
}
)
These keys resemble the dependency arrays passed to React’s useEffect hook. The difference is that changing the key triggers a new query with updated parameters rather than a side-effect function. TanStack Query’s dedicated DevTools package visualizes query state, which helps debug and fine-tune cache behavior. On repeated page views and filter changes, data loads instantly from cache; only the first request shows a loading state and slight delay.
Accessibility As A Technical Requirement
Accessibility is broader than alt text and screen-reader order. For a project like WaterBear, the team evaluated third-party tools specifically for their built-in accessibility support. The video player, for instance, needed full keyboard control, clear focus states, necessary aria-* attributes, and focus trapping inside modals, alongside semantically correct markup.
Icon-only buttons received visually hidden labels so assistive devices can read them, while the SVG icons themselves are hidden from those devices since they add no context.
<!-- Icon button markup with descriptive text for assistive devices -->
<button type="button" class="...">
<svg aria-hidden="true" xmlns="..." width="22" height="22" fill="none">...</svg
><span class="visually-hidden">Open filters</span>
</button>
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
white-space: nowrap;
clip: rect(0 0 0 0);
-webkit-clip-path: inset(50%);
clip-path: inset(50%);
}
Keyboard navigation was a priority, achieved by using proper HTML elements and avoiding click handlers on meaningless div elements—an easy trap in React. Modals presented the real challenge: keyboard users could move focus out of an open modal and interact with the underlying page, which pointer and touch interactions do not permit. The team implemented focus traps using the focus-trap-react library, keeping focus inside modals while open and restoring it to the previously active element on close.
Dynamic Sitemaps For Fresh Content
Sitemaps guide search engines to pages worth crawling, which is faster than waiting for crawlers to find internal links on their own. WaterBear publishes new content regularly, and indexing it quickly matters. Rebuilding and redeploying the entire project each time Sanity content changes was not practical, so the team chose dynamic, server-side sitemaps.
Using the next-sitemap plugin for Next.js, they configured generation for both static and dynamic pages. Custom Sanity queries pull the latest content on each request and generate a fresh sitemap, ensuring new videos appear at the top and are indexed promptly.
The XML output is then available for search engine crawlers:
lastmod value in the sitemap. (Large preview)Performance And Accessibility Takeaways
Across the WaterBear app, several complementary optimizations directly affect perceived and real performance. Images—used prominently across many page types—are handled via a CDN with caching, loading strategies, preloading, and the WebP format. Sanity serves the majority of content, and the team anticipated repeat page views within a session, which justified the TanStack Query caching layer.
Accessibility work covered focus states, keyboard navigation, labels on icon buttons, alt text, and focus traps in modals. Dynamic server-side sitemaps rounded out the set of decisions, making new content discoverable without manual intervention.
The experience of building WaterBear as a first lead-developer role brought both process and technical challenges. Balancing priorities, coordinating a team, and shipping a complex application took deliberate choices. The full project is available on the web, Android, and iOS.



