Engineering
10 min read

How to Optimize Logo API Performance: Caching, Lazy Loading & Core Web Vitals

Advanced techniques to make company logos load instantly: multi-tier caching, lazy loading, WebP/AVIF format selection, and how to measure impact on Core Web Vitals.

Marcus Johnson

Marcus Johnson

Platform Engineer

A poorly implemented logo API integration can silently destroy your Largest Contentful Paint (LCP) and Total Blocking Time (TBT) scores. Logos appear on nearly every page in B2B apps — CRM accounts, invoice line items, email inboxes, search results — and each uncached request adds latency. This guide covers the patterns that keep logos fast at any scale.

Understanding Where Time Goes

Before optimizing, understand the typical breakdown of a logo request:

  • DNS resolution: 10–50ms (first request to a new hostname only)
  • TLS handshake: 20–40ms (first request only with HTTP/2)
  • Network round-trip: 10–200ms (depends on user location and CDN coverage)
  • Server processing: 5–30ms (varies by API)
  • Image decode: 2–15ms (client-side, WebP is faster than PNG)

The biggest wins come from eliminating network round-trips entirely through caching.

Strategy 1: Use the Right Image Format

WebP is 25–35% smaller than PNG for logos. AVIF is 50% smaller but has slightly lower browser support. Always let the browser decide:

tsx
// components/company-logo.tsx
function CompanyLogo({ domain, size = 40 }: { domain: string; size?: number }) {
  const base = `https://img.logorouter.com/${domain}?size=${size * 2}`; // 2x for retina

  return (
    <picture>
      <source srcSet={`${base}&format=avif`} type="image/avif" />
      <source srcSet={`${base}&format=webp`} type="image/webp" />
      <img
        src={`${base}&format=png`}
        alt={`${domain} company logo`}
        width={size}
        height={size}
        loading="lazy"
        decoding="async"
        className="object-contain"
      />
    </picture>
  );
}

Strategy 2: In-Memory Request Deduplication

If your app renders 50 account rows simultaneously and each calls the API for its logo, you will fire 50 parallel requests. Deduplicate with a shared promise cache:

typescript
// lib/logo-cache.ts
const inflight = new Map<string, Promise<string>>();
const resolved = new Map<string, string>();

export async function getLogoUrl(domain: string, size = 64): Promise<string> {
  const key = `${domain}:${size}`;

  if (resolved.has(key)) return resolved.get(key)!;
  if (inflight.has(key)) return inflight.get(key)!;

  const promise = fetch(`https://img.logorouter.com/${domain}?size=${size}&format=webp`)
    .then((res) => {
      const url = res.url; // CDN redirected URL
      resolved.set(key, url);
      inflight.delete(key);
      return url;
    })
    .catch(() => {
      inflight.delete(key);
      return `/fallback-logo.svg`; // graceful fallback
    });

  inflight.set(key, promise);
  return promise;
}

Strategy 3: Browser Cache with Long TTL

LogoRouter CDN responses include Cache-Control: public, max-age=86400, stale-while-revalidate=604800. Make sure your application is not stripping these headers. If you proxy logo requests through your own server, preserve them:

typescript
// app/api/logo/[domain]/route.ts — Next.js proxy that preserves caching
export async function GET(
  request: Request,
  { params }: { params: Promise<{ domain: string }> }
) {
  const { domain } = await params;
  const { searchParams } = new URL(request.url);
  const size = searchParams.get('size') || '64';

  const upstream = await fetch(
    `https://img.logorouter.com/${domain}?size=${size}&format=webp&token=${process.env.LOGOROUTER_API_KEY}`
  );

  return new Response(upstream.body, {
    headers: {
      'Content-Type': upstream.headers.get('Content-Type') || 'image/webp',
      'Cache-Control': 'public, max-age=86400, stale-while-revalidate=604800',
      'CDN-Cache-Control': 'public, max-age=31536000',
    },
  });
}

Strategy 4: Preload Above-the-Fold Logos

For logos that appear in the initial viewport (e.g. a company profile hero), use to fetch them before the browser parses your component tree:

tsx
// In your Next.js page server component
import { headers } from 'next/headers';

export default async function CompanyPage({ params }: { params: { domain: string } }) {
  return (
    <>
      <link
        rel="preload"
        as="image"
        href={`https://img.logorouter.com/${params.domain}?size=128&format=webp`}
        type="image/webp"
      />
      <CompanyHero domain={params.domain} />
    </>
  );
}

Strategy 5: Loading Skeleton to Prevent Layout Shift

Layout shift (CLS) is penalized by Google. If your logo container does not have explicit dimensions, the page will jump when the image loads:

tsx
function LogoWithSkeleton({ domain, size = 40 }: { domain: string; size: number }) {
  const [loaded, setLoaded] = useState(false);

  return (
    <div
      className={cn(
        "relative overflow-hidden rounded-md bg-muted flex-shrink-0",
        !loaded && "animate-pulse"
      )}
      style={{ width: size, height: size }}
    >
      <img
        src={`https://img.logorouter.com/${domain}?size=${size * 2}&format=webp`}
        alt={`${domain} logo`}
        width={size}
        height={size}
        className={cn("object-contain transition-opacity", loaded ? "opacity-100" : "opacity-0")}
        onLoad={() => setLoaded(true)}
        loading="lazy"
        decoding="async"
      />
    </div>
  );
}

Measuring Results

Track these metrics before and after implementing these strategies:

javascript
// Measure logo load performance
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.name.includes('logorouter.com') || entry.name.includes('img.logorouter')) {
      console.log(`Logo loaded: ${entry.name}`);
      console.log(`  Duration: ${entry.duration.toFixed(0)}ms`);
      console.log(`  Transfer: ${entry.transferSize} bytes`);
      console.log(`  From cache: ${entry.transferSize === 0}`);
    }
  }
});
observer.observe({ entryTypes: ['resource'] });

With the full stack of optimizations above, you should see:

  • 80–95% cache hit rate after warm-up
  • Sub-10ms logo loads from browser cache
  • Zero CLS from logo containers
  • 30–50% reduction in logo-related bandwidth

LogoRouter's global CDN is already optimized for you

Every request is served from the nearest of our 300+ edge locations. Sub-50ms globally. Start on the free tier and scale up.

Community — 500K req/mo free
Get your free API key
Start building today

Company logos and brand data, ready in 60 seconds

500,000 requests per month, completely free. No credit card. No contracts. Upgrade to a paid plan when you are ready to scale.

  • 500K requests / month free
  • 30M+ company logos
  • Sub-50ms global CDN
  • PNG, WebP & SVG formats
  • No credit card required

Topics covered

Engineering
performance
caching
lazy loading
core web vitals
webp
cdn