icon.dev
NPM Package

Framework Integration

Use icondev with Next.js, Nuxt, Remix, and more

Overview

The icondev package integrates seamlessly with modern JavaScript frameworks. This guide covers framework-specific configurations and best practices.


Next.js

App Router (Next.js 13+)

Icons work out of the box in Server Components during SSR (reads from icondev.json). For client-side usage, use Client Components:

app/components/icon-button.tsx
'use client'

import { Icon } from 'icondev/react'

export function IconButton({ icon, label, onClick }) {
  return (
    <button onClick={onClick} className="flex items-center gap-2">
      <Icon name={icon} size={20} />
      <span>{label}</span>
    </button>
  )
}
app/page.tsx
import { IconButton } from './components/icon-button'

export default function Page() {
  return (
    <div>
      <h1>Welcome</h1>
      <IconButton icon="arrow-right" label="Continue" onClick={() => {}} />
    </div>
  )
}

In Node.js/SSR environments, icons load from icondev.json. In browser, you need IconDevProvider (see below).

Browser-Only Usage (IconDevProvider)

If you're using icons only on the client side without SSR, wrap your app with IconDevProvider:

app/layout.tsx
'use client'

import { IconDevProvider } from 'icondev/react'

const config = {
  apiKey: 'pk_...',
  projectSlug: 'my-project',
  cdnUrl: 'https://cdn.icon.dev'
}

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <IconDevProvider config={config}>
          {children}
        </IconDevProvider>
      </body>
    </html>
  )
}

Preloading Critical Icons

app/layout.tsx
'use client'

import { useEffect } from 'react'
import { preloadIcons } from 'icondev'

export default function RootLayout({ children }) {
  useEffect(() => {
    // Preload icons used in layout
    preloadIcons(['logo', 'menu', 'close'])
  }, [])

  return (
    <html>
      <body>{children}</body>
    </html>
  )
}

Nuxt 3

Setup

Icons work automatically in Nuxt 3! The package reads from icondev.json during SSR.

app.vue
<script setup>
import { Icon } from 'icondev/vue'
</script>

<template>
  <div>
    <Icon name="home" :size="24" />
  </div>
</template>

Preload Icons Plugin

Create a plugin to preload critical icons:

plugins/icondev.client.ts
import { preloadIcons } from 'icondev'

export default defineNuxtPlugin(() => {
  // Preload critical icons
  preloadIcons(['logo', 'menu', 'close'])
})

The .client.ts suffix ensures the plugin only runs on the client side.

Using Icons in Components

components/IconButton.vue
<script setup lang="ts">
import { Icon } from 'icondev/vue'

defineProps<{
  icon: string
  label: string
}>()
</script>

<template>
  <button class="flex items-center gap-2">
    <Icon :name="icon" :size="20" />
    <span>{{ label }}</span>
  </button>
</template>
pages/index.vue
<script setup lang="ts">
import IconButton from '~/components/IconButton.vue'
</script>

<template>
  <div>
    <h1>Welcome</h1>
    <IconButton icon="arrow-right" label="Continue" />
  </div>
</template>

Auto-imports

Add to nuxt.config.ts for auto-imports:

nuxt.config.ts
export default defineNuxtConfig({
  imports: {
    imports: [
      {
        from: 'icondev/vue',
        name: 'Icon'
      },
      {
        from: 'icondev/vue',
        name: 'useIcon'
      }
    ]
  }
})

Now use without imports:

<template>
  <Icon name="home" :size="24" />
</template>

Remix

Setup

Icons work automatically in Remix! The package reads from icondev.json during SSR.

app/routes/_index.tsx
import { Icon } from 'icondev/react'

export default function Index() {
  return (
    <div>
      <h1>Welcome to Remix</h1>
      <Icon name="home" size={24} />
    </div>
  )
}

Root Layout Preloading

app/root.tsx
import { useEffect } from 'react'
import { preloadIcons } from 'icondev'
import { Links, Meta, Outlet, Scripts } from '@remix-run/react'

export default function App() {
  useEffect(() => {
    preloadIcons(['logo', 'menu'])
  }, [])

  return (
    <html>
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet />
        <Scripts />
      </body>
    </html>
  )
}

Vite + React

Standard Setup

src/App.tsx
import { Icon } from 'icondev/react'

function App() {
  return (
    <div>
      <Icon name="home" size={24} />
    </div>
  )
}

export default App

Browser-Only (IconDevProvider)

For pure client-side apps without SSR:

src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { IconDevProvider } from 'icondev/react'
import App from './App'

const config = {
  apiKey: 'pk_...',
  projectSlug: 'my-project',
  cdnUrl: 'https://cdn.icon.dev'
}

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <IconDevProvider config={config}>
      <App />
    </IconDevProvider>
  </StrictMode>
)

Preload on Mount

src/main.tsx
import { StrictMode, useEffect } from 'react'
import { createRoot } from 'react-dom/client'
import { preloadIcons } from 'icondev'
import App from './App'

function Root() {
  useEffect(() => {
    preloadIcons(['logo', 'menu', 'close'])
  }, [])

  return <App />
}

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <Root />
  </StrictMode>
)

Astro

React Integration

src/components/IconButton.tsx
'use client'

import { Icon } from 'icondev/react'

export function IconButton({ icon, label }) {
  return (
    <button>
      <Icon name={icon} size={20} />
      <span>{label}</span>
    </button>
  )
}
src/pages/index.astro
---
import { IconButton } from '../components/IconButton'
---

<html>
  <body>
    <h1>Welcome</h1>
    <IconButton client:load icon="arrow-right" label="Continue" />
  </body>
</html>

Use client:load directive to ensure icons render on the client.


Svelte / SvelteKit

Using Core API

While we don't have an official Svelte adapter yet, you can use the core API:

src/lib/Icon.svelte
<script>
  import { onMount } from 'svelte'
  import { loadIcon } from 'icondev'

  export let name
  export let size = 24

  let svg = ''
  let loading = true

  onMount(async () => {
    try {
      const result = await loadIcon({ name })
      svg = result.svg
    } catch (error) {
      console.error('Failed to load icon:', error)
    } finally {
      loading = false
    }
  })
</script>

{#if loading}
  <div class="skeleton" style="width: {size}px; height: {size}px;" />
{:else}
  {@html svg}
{/if}
src/routes/+page.svelte
<script>
  import Icon from '$lib/Icon.svelte'
</script>

<Icon name="home" size={24} />

Angular

Using Core API

Use the core API with Angular:

src/app/icon.component.ts
import { Component, Input, OnInit } from '@angular/core'
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
import { loadIcon } from 'icondev'

@Component({
  selector: 'app-icon',
  template: `
    <div *ngIf="loading">Loading...</div>
    <div *ngIf="!loading && svg" [innerHTML]="svg"></div>
  `,
  standalone: true
})
export class IconComponent implements OnInit {
  @Input() name!: string
  @Input() size: number = 24

  svg: SafeHtml | null = null
  loading = true

  constructor(private sanitizer: DomSanitizer) {}

  async ngOnInit() {
    try {
      const result = await loadIcon({ name: this.name })
      this.svg = this.sanitizer.bypassSecurityTrustHtml(result.svg)
    } catch (error) {
      console.error('Failed to load icon:', error)
    } finally {
      this.loading = false
    }
  }
}
src/app/app.component.ts
import { Component } from '@angular/core'
import { IconComponent } from './icon.component'

@Component({
  selector: 'app-root',
  template: `
    <div>
      <h1>Welcome</h1>
      <app-icon name="home" [size]="24"></app-icon>
    </div>
  `,
  standalone: true,
  imports: [IconComponent]
})
export class AppComponent {}

Server-Side Rendering (SSR)

How It Works

Icons work seamlessly with SSR:

  1. In Node.js/SSR: Package reads icon metadata from icondev.json
  2. In Browser (with SSR): Icons load from CDN using URLs from icondev.json
  3. In Browser (without SSR): Requires IconDevProvider to configure CDN URLs

Best Practices

✅ DO: Use icons in SSR frameworks (Next.js, Nuxt, Remix)

// Works automatically in SSR
import { Icon } from 'icondev/react'

export default function Page() {
  return <Icon name="home" />
}

✅ DO: Use IconDevProvider for client-only apps

import { IconDevProvider } from 'icondev/react'

function App() {
  return (
    <IconDevProvider config={{ apiKey: 'pk_...', projectSlug: 'my-project' }}>
      <YourApp />
    </IconDevProvider>
  )
}

Loading States

Add skeleton loaders for better UX:

'use client'

import { Icon } from 'icondev/react'
import { useState, useEffect } from 'react'

export function IconWithSkeleton({ name }) {
  const [mounted, setMounted] = useState(false)

  useEffect(() => setMounted(true), [])

  if (!mounted) {
    return <div className="skeleton w-6 h-6" />
  }

  return <Icon name={name} size={24} />
}

Build Optimization

Tree Shaking

The package supports tree shaking. Only import what you need:

// ✅ Good: Only imports Icon component
import { Icon } from 'icondev/react'

// ❌ Avoid: Imports everything
import * as IconDev from 'icondev'

Code Splitting

Lazy load icons for better performance:

import { lazy, Suspense } from 'react'

const IconGallery = lazy(() => import('./components/IconGallery'))

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <IconGallery />
    </Suspense>
  )
}

Next Steps