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.

Amit Yadav
Amit Yadav

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>
  );
}
Tip

You only need 'use client' if the icon is inside a Client Component (e.g., a component with useState, useEffect, or event handlers).

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" />
Warning

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

Yes. Lucide React, Heroicons, Tabler Icons, and Phosphor all work in Server Components. They export pure SVG elements with no client-side hooks, so no 'use client' directive is needed.

Lucide React is the best choice for most Next.js projects: excellent TypeScript support, tree-shakeable, 1,500+ icons, MIT licensed, and fully compatible with the App Router and Server Components.

Import the icon component from your chosen library and use it inside the layout JSX. For lucide-react: import { Home } from 'lucide-react' and render . No special Next.js setup needed.

Yes. Copy SVG code from AllSVGIcons and paste it directly into your JSX. In JSX, replace class with className. This avoids library dependencies entirely and works in any component type.

Place an icon.svg file inside the /app directory. Next.js will serve it as the favicon automatically. You can also use icon.tsx to generate it dynamically with ImageResponse.
Share this post