Next.js SVG Icons – Server Components, App Router & Best Libraries
How to use SVG icons in Next.js 14+ with the App Router, Server Components, and Suspense. Compare lucide-react, heroicons, and other libraries.
Next.js 13+ App Router changes how icons work: Server Components, React Server Components, and Suspense affect which icon libraries you can use and how. Here’s the complete guide.
Best SVG icons for Next.js
All five libraries above ship ESM and work in Next.js Server Components without any setup.
Installation
npm install lucide-react
# or
npm install @heroicons/react
# or
npm install @tabler/icons-react
Server Components (no ‘use client’)
Icon components from Lucide, Heroicons, and Tabler are pure SVGs — no state, no hooks. You can import them directly in Server Components:
// app/dashboard/page.tsx
import { LayoutDashboard, FileText, Settings } from 'lucide-react';
export default function DashboardPage() {
return (
<div className="space-y-4">
<h1 className="flex items-center gap-2 text-xl font-semibold">
<LayoutDashboard size={24} aria-hidden="true" />
Dashboard
</h1>
</div>
);
}
You only need 'use client' if the icon is inside a Client Component (e.g., a component with useState, useEffect, or event handlers).
Sidebar with active state (Client Component)
Sidebars need usePathname() which requires a Client Component:
// components/Sidebar.tsx
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { LayoutDashboard, Users, Settings, FileText } from 'lucide-react';
const links = [
{ href: '/dashboard', label: 'Dashboard', icon: LayoutDashboard },
{ href: '/users', label: 'Users', icon: Users },
{ href: '/posts', label: 'Posts', icon: FileText },
{ href: '/settings', label: 'Settings', icon: Settings },
];
export function Sidebar() {
const pathname = usePathname();
return (
<nav className="flex flex-col gap-1">
{links.map(({ href, label, icon: Icon }) => (
<Link
key={href}
href={href}
className={`flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors
${pathname === href
? 'bg-muted text-primary'
: 'text-muted-foreground hover:bg-muted'
}`}
>
<Icon size={18} aria-hidden="true" />
{label}
</Link>
))}
</nav>
);
}
Icon component pattern
Create a small wrapper so you can swap libraries later without touching every usage:
// components/Icon.tsx
import * as lucide from 'lucide-react';
type IconName = keyof typeof lucide;
interface IconProps {
name: IconName;
size?: number;
className?: string;
}
export function Icon({ name, size = 20, className }: IconProps) {
const LucideIcon = lucide[name] as React.ComponentType<{
size?: number;
className?: string;
'aria-hidden'?: boolean;
}>;
if (!LucideIcon) return null;
return <LucideIcon size={size} className={className} aria-hidden />;
}
Usage:
<Icon name="Search" size={20} className="text-primary" />
<Icon name="Bell" size={20} />
Using next/image with SVG
For SVG files in your /public folder, use Next.js Image with unoptimized or as a plain <img>:
import Image from 'next/image';
// next.config has images.dangerouslyAllowSVG: true
<Image src="/icons/logo.svg" width={24} height={24} alt="" />
// Or with a plain img tag (no optimization, fine for icons)
<img src="/icons/logo.svg" width={24} height={24} alt="" aria-hidden="true" />
Avoid next/image for inline SVG icons from a library — use the React component directly instead. next/image is for file-based assets, not JSX SVGs.
Metadata icons (favicon, apple-icon)
Next.js App Router supports icon metadata files in /app:
app/
favicon.ico ← browser favicon
icon.svg ← theme-color SVG favicon
apple-icon.png ← Apple touch icon
opengraph-image.png ← Social preview
You can generate them dynamically:
// app/icon.tsx
import { ImageResponse } from 'next/og';
export const size = { width: 32, height: 32 };
export const contentType = 'image/png';
export default function Icon() {
return new ImageResponse(
<div style={{ background: '#6366f1', width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 8 }}>
<span style={{ color: 'white', fontSize: 20 }}>S</span>
</div>,
{ ...size }
);
}
Frequently asked questions
Explore more resources