icon.dev
NPM Package

Best Practices

Performance optimization and recommended patterns

Preload Critical Icons

Preload icons that appear immediately on page load:

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

function App() {
  useEffect(() => {
    // Preload above-the-fold icons
    preloadIcons(['logo', 'menu', 'close', 'home'])
  }, [])

  return <YourApp />
}

Preloading eliminates loading flashes and improves perceived performance.

When to preload:

  • Navigation icons (menu, close, back)
  • Brand icons (logo, company icons)
  • Critical UI elements above the fold
  • Modal/dialog icons before opening

Optimize Bundle Size

Tree Shaking

Only import what you need for maximum tree-shaking:

// ✅ Good: Specific imports
import { Icon } from 'icondev/react'
import { preloadIcons } from 'icondev'

// ❌ Bad: Wildcard imports
import * as IconDev from 'icondev'

Code Splitting

Lazy load icon-heavy components:

import { lazy, Suspense } from 'react'

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

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

Caching Strategy

Memory Cache

Icons are automatically cached in memory after first load:

// First render: fetches from CDN (~20ms)
<Icon name="home" />

// Second render: instant (from memory cache)
<Icon name="home" />

Memory cache persists until page refresh or clearCache() is called.

Clear Cache When Needed

Only clear cache for memory management (rare):

import { clearCache } from 'icondev'

// Clear cache when unmounting icon-heavy component
useEffect(() => {
  return () => {
    if (hasLoaded1000PlusIcons) {
      clearCache()
    }
  }
}, [])

Loading States

Skeleton Loaders

Show skeleton while icons load:

import { useIcon } from 'icondev/react'

function IconWithSkeleton({ name }) {
  const { svg, loading, error } = useIcon(name)

  if (loading) {
    return (
      <div className="animate-pulse">
        <div className="w-6 h-6 bg-gray-200 rounded" />
      </div>
    )
  }

  if (error) return <span>❌</span>

  return <div dangerouslySetInnerHTML={{ __html: svg }} />
}

Preload Before Showing

Preload icons before showing them:

function Modal({ isOpen }) {
  useEffect(() => {
    if (isOpen) {
      // Preload modal icons when opening
      preloadIcons(['close', 'checkmark', 'warning'])
    }
  }, [isOpen])

  return isOpen && (
    <dialog>
      <Icon name="close" />
      {/* ... */}
    </dialog>
  )
}

SSR Best Practices

Node.js/SSR Environments

Icons automatically read from icondev.json in SSR:

// Works automatically in Next.js, Nuxt, Remix
import { Icon } from 'icondev/react'

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

No special configuration needed for SSR frameworks!

Client-Only Apps

For pure client-side apps, use IconDevProvider:

import { IconDevProvider } from 'icondev/react'

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

function App() {
  return (
    <IconDevProvider config={config}>
      <YourApp />
    </IconDevProvider>
  )
}

Avoid Hydration Mismatches

Use consistent rendering approach:

'use client'

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

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

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

  // Show skeleton during SSR, real icon after hydration
  if (!mounted) {
    return <div className="w-6 h-6 bg-gray-200 animate-pulse" />
  }

  return <Icon name={name} {...props} />
}

Error Handling

Graceful Degradation

Always handle icon loading errors:

<Icon
  name="home"
  onError={(error) => {
    console.error('Icon failed to load:', error)
    // Could also use fallback UI
  }}
/>

Fallback Icons

Use fallback UI when icons fail:

import { useIcon } from 'icondev/react'

function IconWithFallback({ name }) {
  const { svg, loading, error } = useIcon(name)

  if (loading) return <Skeleton />
  if (error) return <span>📦</span> // Emoji fallback

  return <div dangerouslySetInnerHTML={{ __html: svg }} />
}

Performance Monitoring

Measure Icon Load Time

Track icon loading performance:

import { loadIcon } from 'icondev'

async function measureIconLoad(name) {
  const start = performance.now()

  await loadIcon({ name })

  const end = performance.now()
  console.log(`Icon "${name}" loaded in ${end - start}ms`)
}

Expected Performance

  • First load (CDN): 20-50ms
  • Cached load (memory): <1ms
  • Preloaded icons: <1ms

If icons take >100ms consistently, check your CDN latency or network.


Security

Environment Variables

Store API keys in environment variables:

// .env.local
NEXT_PUBLIC_ICONDEV_KEY=pk_...
// usage
const config = {
  apiKey: process.env.NEXT_PUBLIC_ICONDEV_KEY,
  projectSlug: 'my-project'
}

Git & Version Control

Important: icondev.json contains your icon list AND API key.

Recommended approach:

  1. Commit icondev.json to git - Your app needs the icon list to work
  2. Use environment variables in production - Override API key in production:
// In production, load API key from env
if (process.env.NODE_ENV === 'production') {
  // API key comes from env vars in production
  process.env.ICONDEV_API_KEY = process.env.ICONDEV_API_KEY || 'pk_...'
}

Alternative: .gitignore + CI/CD

If you don't want to commit the API key:

# .gitignore
icondev.json

Then run npx icondev sync in your CI/CD pipeline before deployment.

Public keys (pk_) are read-only and safe to commit. Secret keys (sk_) should NEVER be committed.

Public vs Private Keys

  • Public keys (pk_): Safe to use in browser, read-only access
  • Secret keys (sk_): Never use in browser, write access to project
// ✅ Safe: Public key in browser
const config = {
  apiKey: 'pk_...'
}

// ❌ Dangerous: Secret key in browser
const config = {
  apiKey: 'sk_...' // Never do this!
}

TypeScript

Type Safety

Use TypeScript for better DX:

import type { IconProps } from 'icondev'

const iconConfig: IconProps = {
  name: 'home',
  size: 24,
  color: 'currentColor'
}

Future: Auto-generated Icon Names

Coming soon - auto-generated icon name types:

// Future feature
type IconName = 'home' | 'search' | 'user' | 'arrow-right'

<Icon name="home" /> // ✅ Valid
<Icon name="invalid" /> // ❌ Type error

Testing

Mock Icons in Tests

Mock the icondev package in tests:

// __mocks__/icondev.js
export const Icon = ({ name }) => <div data-testid={`icon-${name}`} />
export const loadIcon = async ({ name }) => ({ svg: `<svg>${name}</svg>` })
export const preloadIcons = async () => {}
// jest.config.js
module.exports = {
  moduleNameMapper: {
    '^icondev/react$': '<rootDir>/__mocks__/icondev.js'
  }
}

Test Icon Rendering

import { render, screen } from '@testing-library/react'
import { Icon } from 'icondev/react'

test('renders icon', async () => {
  render(<Icon name="home" />)

  const icon = await screen.findByTestId('icon-home')
  expect(icon).toBeInTheDocument()
})

Accessibility

Add ARIA Labels

Make icons accessible:

<button aria-label="Open menu">
  <Icon name="menu" />
</button>

Decorative Icons

Hide decorative icons from screen readers:

<div>
  <Icon name="star" aria-hidden="true" />
  <span>Featured</span>
</div>

Semantic Icons

Use semantic HTML with icons:

<button aria-label="Close dialog">
  <Icon name="close" />
  <span className="sr-only">Close</span>
</button>

Next Steps