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.
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:
// 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:
// 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:
// 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:
// 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:
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:
// 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 freeCompany 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
Related articles
View allIntegrating a Company Logo API with Next.js 15 (App Router)
The complete guide to adding company logos to a Next.js 15 App Router app: server components, image optimization, caching, and the Next.js Image component configuration.
Building a Production-Ready Company Logo Component in React
The definitive guide to a React logo component that handles loading states, error fallbacks, retina display, accessibility, and TypeScript — ready to drop into any codebase.