← Binderdex dev log

2026-07-24

Desktop QR handoff and image loading that does not flash gray

webproducttcg

When your social bio link meets a desktop browser

Here is the problem: someone clicks your BinderDex link from an Instagram or TikTok bio. On mobile, that is easy: a OneLink redirect carries them straight to the right app store with attribution intact. On desktop, you used to land on a generic homepage chooser with no idea where they came from.

Today that changes. Desktop visitors from owned social profiles now get a dedicated QR handoff page. Each QR encodes the same source-specific install URL the mobile redirect uses, so the phone scan still hits the matching OneLink while the desktop pageview keeps its attribution parameters for analytics.

Short links that stay readable

The profile bio links are now short branded paths like /app/tiktok and /app/x instead of long query-string URLs. The source has to stay in the path because referrers are unreliable across app-store handoffs. If you rely on the referrer header to tell you where a visitor came from, it will be missing by the time they land in the store.

// The route reuses the canonical campaign mapping
// instead of creating a second attribution scheme.
const OWNED_SOCIAL_PROFILES = {
  tiktok: { publicUrl: "/app/tiktok", qrImageSrc: "/marketing/social-install-tiktok-qr.svg" },
  x:      { publicUrl: "/app/x",      qrImageSrc: "/marketing/social-install-x-qr.svg" },
  // ...
} as const;

That snippet is simplified from the real route config, but the shape is honest: one profile object per source, each carrying its own QR asset and short URL. No second attribution system running in parallel.

Images that do not flash gray

Separate fix in the same session: cold homepage and catalog images were showing empty gray surfaces while Next.js image optimization re-rendered at the origin. The catalog assets were already browser and CDN cached, but the /_next/image optimization layer had no CloudFront cache policy of its own.

The fix was a new CloudFront cache policy keyed by URL, width, quality, Host, and Accept header (keeping cookies out of the cache key), plus a shared LoadAwareImage component that handles the loading state in the UI:

// Three states: loading (shimmer), error (fallback text), loaded (fade in).
// Respects motion-reduce preferences automatically.
{status === "loading" ? (
  
) : null}
{status === "error" ? (
  
    {fallbackText}
  
) : null}

The component fades images in on load instead of popping them, and shows a shimmer placeholder during loading instead of a gray box. If an image truly fails, you get a text fallback instead of a broken-image icon.

Also: homepage screenshots are now hashed static imports with generated blur placeholders, below-the-fold feature imagery is no longer preloaded, and decorative hero images have lowered fetch priority. Small changes, but the cold-load experience went from gray flashing to a calm shimmer-to-fade.

this is the build log of binderdex · www.binderdex.com · all entries · essays