Integrating 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.
Next.js 15 introduced significant changes to how images, caching, and server components work. This guide covers the idiomatic way to integrate a company logo API with the App Router — including the next.config.js changes, proper use of the Next.js Image component, and server-side caching patterns.
Initial Setup
First, add the LogoRouter domain to your next.config.ts so Next.js Image can optimize and proxy logos:
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'img.logorouter.com',
pathname: '/**',
},
],
},
};
export default nextConfig;Using next/image with LogoRouter
The Image component handles sizing, format optimization, and lazy loading automatically:
// components/company-logo-next.tsx
import Image from 'next/image';
interface Props {
domain: string;
size?: number;
className?: string;
}
export function CompanyLogoNext({ domain, size = 40, className }: Props) {
return (
<Image
src={`https://img.logorouter.com/${domain}?size=${size * 2}&format=png`}
alt={`${domain} logo`}
width={size}
height={size}
className={className}
unoptimized // LogoRouter already optimizes — skip double-optimization
/>
);
}Use unoptimized when the source is already a CDN-optimized image. Double-optimizing wastes your Next.js image optimization quota and adds latency.Server Components: Fetching Data Alongside Logos
In a Server Component, you can fetch company data and resolve the logo URL in the same render pass:
// app/accounts/[domain]/page.tsx
import { Suspense } from 'react';
import Image from 'next/image';
interface PageProps {
params: Promise<{ domain: string }>;
}
export default async function AccountPage({ params }: PageProps) {
const { domain } = await params;
// These run in parallel
const [account, enrichment] = await Promise.all([
getAccount(domain),
fetch(`https://api.logorouter.com/v3/${domain}/enrichment`, {
headers: { Authorization: `Bearer ${process.env.LOGOROUTER_API_KEY}` },
next: { revalidate: 3600, tags: [`logo-${domain}`] }, // Next.js cache tags
}).then(r => r.ok ? r.json() : null),
]);
return (
<main>
<header className="flex items-center gap-4 p-6 border-b">
<Image
src={`https://img.logorouter.com/${domain}?size=128&format=webp`}
alt={`${account.name} logo`}
width={64}
height={64}
className="rounded-xl object-contain"
unoptimized
priority // Above the fold — don't lazy load
/>
<div>
<h1 className="text-xl font-semibold">{account.name}</h1>
{enrichment && (
<p className="text-sm text-muted-foreground">{enrichment.industry?.primary}</p>
)}
</div>
</header>
</main>
);
}Caching Strategy with Cache Tags
Use Next.js cache tags to invalidate specific company logos when they update:
// app/api/webhooks/logorouter/route.ts
import { revalidateTag } from 'next/cache';
export async function POST(req: Request) {
const { type, data } = await req.json();
if (type === 'logo.updated') {
// Revalidate all pages that used this company's logo
revalidateTag(`logo-${data.domain}`, 'max');
}
return Response.json({ ok: true });
}Building a Logo Gallery with Streaming
For pages that show many logos (like a customer grid or partnership page), use Suspense streaming to progressively load:
// app/customers/page.tsx
import { Suspense } from 'react';
const CUSTOMERS = ['stripe.com', 'notion.so', 'linear.app', 'vercel.com', 'figma.com'];
function LogoGrid() {
return (
<div className="grid grid-cols-5 gap-8 items-center">
{CUSTOMERS.map((domain) => (
<div key={domain} className="flex justify-center">
<Image
src={`https://img.logorouter.com/${domain}?size=128&format=webp`}
alt={`${domain} customer logo`}
width={80}
height={40}
className="object-contain opacity-60 hover:opacity-100 transition-opacity"
unoptimized
/>
</div>
))}
</div>
);
}
export default function CustomersPage() {
return (
<section className="py-24">
<h2 className="text-center text-sm text-muted-foreground uppercase tracking-widest mb-12">
Trusted by teams at
</h2>
<Suspense fallback={<LogoGridSkeleton count={CUSTOMERS.length} />}>
<LogoGrid />
</Suspense>
</section>
);
}Error Boundaries for Logo Failures
Wrap logo sections in error boundaries so a single broken logo does not crash your page:
// components/logo-error-boundary.tsx
'use client';
import { Component, type ReactNode } from 'react';
export class LogoErrorBoundary extends Component<
{ children: ReactNode; fallback?: ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? <div className="size-10 rounded-md bg-muted" />;
}
return this.props.children;
}
}Works perfectly with Next.js — zero configuration
LogoRouter is designed for modern Next.js apps. Start on the free tier and add your API key when you are ready to scale.
Community — free, no credit cardCompany 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 allBuilding Dynamic UI Themes from Company Brand Colors in React
Step-by-step tutorial: automatically adapt your app's UI to match any company's brand colors using LogoRouter's Colors API and React CSS custom properties.
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.
Display Sender Company Logos in Your Email App (Like Superhuman)
Tutorial: how to identify company domains from email addresses and display brand logos in your email client's inbox view — with error handling, caching, and fallbacks.