A complete Next.js 15+ plugin scaffold that automatically generates SVG sprites from individual icon files, provides a /icons route to serve them, and includes a reusable <Icon> React component.
- 🎨 Automatic SVG Sprite Generation - Generates separate sprite files per namespace
- 🚀 Built-in Static Routes - Serves sprites with proper caching headers (SSG/Static)
- ⚛️ Reusable Icon Component - Easy-to-use React component with automatic namespace detection
- 🔄 Hot Reload Support - Regenerates sprites during development (with file watcher)
- 📦 Zero Configuration - Works out of the box with sensible defaults
- ♿ Accessible - Includes
IconWithLabelcomponent for accessibility - 🔷 TypeScript Support - Full type safety with autocomplete for icon names
- 💡 IntelliSense - Get icon name suggestions as you type
- 🔧 Automatic Name Normalization - Converts all naming conventions to kebab-case
- 📁 Namespace Support - Separate sprite files per folder (e.g.,
social/→icons-social.svg) - 🔍 Duplicate Detection - Shows build-time errors when multiple files normalize to the same name
- ⚡ Performance Optimized - Each namespace loads only its own icons
- 🚀 NEW: Dynamic Loading - Client-side lazy loading with intelligent caching (no route setup needed!)
npm installPlace your SVG icon files in the svg-icons/ directory:
svg-icons/
├── home.svg
├── user.svg
├── settings.svg
└── search.svg
With Namespaces (optional): Organize icons in subdirectories for namespacing:
svg-icons/
├── home.svg
├── user.svg
├── social/
│ ├── facebook.svg
│ └── twitter.svg
└── brands/
├── apple.svg
└── google.svg
This creates:
- Namespaced icons:
social:facebook,social:twitter,brands:apple,brands:google - Separate sprite files:
icons-social.svg,icons-brands.svg - Root level icons in:
icons-sprite.svg(default namespace)
npm run build:spriteThis generates separate sprite files in public/:
public/icons-sprite.svg- Default/root level iconspublic/icons-social.svg- Social namespace iconspublic/icons-brands.svg- Brands namespace icons
JavaScript:
import Icon from '@/components/Icon';
export default function MyComponent() {
return (
<div>
<Icon name="home" size={24} />
<Icon name="user" size={32} color="blue" />
<Icon name="settings" size={24} className="my-icon" />
</div>
);
}TypeScript (with autocomplete!):
import Icon from '@/components/Icon';
export default function MyComponent() {
return (
<div>
{/* Your IDE will autocomplete icon names: home, user, settings, search */}
<Icon name="home" size={24} />
<Icon name="user" size={32} color="blue" />
<Icon name="settings" size={24} className="my-icon" />
</div>
);
}📘 TypeScript Users: See TYPESCRIPT.md for complete TypeScript documentation including type-safe icon names and autocomplete features.
nextjs-svgsprite/
├── app/
│ ├── icons/
│ │ └── route.ts # Route handler for /icons endpoint
│ ├── layout.tsx # Root layout
│ └── page.tsx # Example page
├── components/
│ ├── Icon.tsx # Reusable Icon component (TypeScript)
│ └── icon-types.ts # Auto-generated icon types
├── scripts/
│ └── build-sprite.js # Sprite generation script
├── svg-icons/ # Place your SVG files here
│ ├── home.svg
│ ├── user.svg
│ ├── settings.svg
│ └── search.svg
├── public/
│ └── icons-sprite.svg # Generated sprite (gitignored)
├── next.config.js # Next.js config with plugin
├── nextjs-svgsprite.js # Plugin implementation
├── tsconfig.json # TypeScript configuration
└── package.json
In next.config.js:
const withSvgSprite = require('./nextjs-svgsprite');
const nextConfig = {
// Your Next.js config
};
module.exports = withSvgSprite()(nextConfig);const withSvgSprite = require('./nextjs-svgsprite');
const nextConfig = {
// Your Next.js config
};
module.exports = withSvgSprite({
svgDir: 'svg-icons', // Directory with SVG files
outputPath: 'public/icons-sprite.svg', // Output path for sprite
})(nextConfig);<Icon
name="home" // Required: icon name (without .svg extension)
size={24} // Optional: icon size in pixels (default: 24)
color="currentColor" // Optional: icon color (default: currentColor)
className="" // Optional: CSS classes
style={{}} // Optional: inline styles
/>import { IconWithLabel } from '@/components/Icon';
<IconWithLabel
name="home"
label="Home page" // Accessible label for screen readers
size={24}
/>;The new IconDynamic component provides client-side lazy loading with intelligent caching, eliminating the need for manual route setup. Perfect for client-heavy applications and npm packages!
- ✅ No Route Setup Required - Just import and use, no need to copy route files
- ✅ Lazy Loading - Sprites loaded on-demand per namespace
- ✅ Smart Caching - In-memory + session storage with version management
- ✅ Easy Integration - Works out of the box in any Next.js project
- ✅ Version Management - Automatic cache invalidation on version changes
'use client';
import IconDynamic from '@/components/IconDynamic';
export default function MyComponent() {
return (
<div>
{/* Default namespace icon */}
<IconDynamic name="home" size={24} />
{/* Namespaced icon - loads 'social' sprite on first use */}
<IconDynamic name="social:facebook" size={32} />
{/* Another namespaced icon - reuses cached 'social' sprite */}
<IconDynamic name="social:twitter" size={32} />
</div>
);
}Preload Namespaces:
import { preloadSprites } from '@/components/IconDynamic';
useEffect(() => {
preloadSprites(['social', 'brands']); // Preload common namespaces
}, []);Clear Cache:
import { clearSpriteCache } from '@/components/IconDynamic';
clearSpriteCache(); // Clear all cached spritesCheck Version:
import { getSpriteVersion } from '@/components/IconDynamic';
console.log('Sprite version:', getSpriteVersion());Use Icon (Static) when:
- You're using server components
- You need maximum performance with static routes
- You don't mind setting up route handlers
Use IconDynamic (Dynamic) when:
- You want zero configuration
- You're building a client-heavy application
- You're publishing an npm package
- You need lazy loading and intelligent caching
- You want built-in version management
📘 Full Documentation: See DYNAMIC_LOADING.md for complete documentation on dynamic loading, caching strategies, and advanced usage.
Starts the development server with hot reload. The sprite is regenerated automatically when SVG files change.
Manually generates the SVG sprite from all files in svg-icons/.
Builds the sprite and then builds the Next.js application for production.
Starts the production server.
This package provides full TypeScript support with type-safe icon names and autocomplete suggestions!
When you run npm run build:sprite, the script automatically:
- ✅ Generates TypeScript type definitions from your SVG files
- ✅ Creates a union type of all available icon names
- ✅ Enables IDE autocomplete for icon names
import Icon from '@/components/Icon';
export default function MyComponent() {
return (
<>
{/* ✅ Valid - TypeScript knows "home" exists */}
<Icon name="home" size={24} />
{/* ❌ TypeScript error - "invalid" icon doesn't exist */}
<Icon name="invalid" size={24} />
{/* 💡 Your IDE will suggest: "home" | "user" | "settings" | "search" */}
<Icon name="" size={24} />
</>
);
}The build script creates components/icon-types.ts:
export type IconName = 'home' | 'search' | 'settings' | 'user';
export const iconNames = ['home', 'search', 'settings', 'user'] as const;- 🔷 Type Safety: Catch invalid icon names at compile time
- 💡 Autocomplete: Your IDE suggests available icon names as you type
- 📝 Self-Documenting: Types serve as inline documentation
- 🔄 Always in Sync: Types update automatically when you add/remove icons
For complete TypeScript documentation, including:
- Advanced type usage
- Migration from JavaScript
- IDE setup and configuration
- Troubleshooting
See TYPESCRIPT.md
All icon names are automatically normalized to kebab-case format. This means you can use any naming convention in your SVG files, and they'll be standardized:
| Original Filename | Normalized Name |
|---|---|
sunMoon.svg |
sun-moon |
SunMoon.svg |
sun-moon |
sun_moon.svg |
sun-moon |
sun moon.svg |
sun-moon |
sun-moon.svg |
sun-moon |
sun moon.svg |
sun-moon (multiple spaces) |
If multiple files normalize to the same name, the plugin will:
⚠️ Show a warning listing all duplicate files- Keep only the first occurrence
- Remove the duplicates from the final sprite
Example warning:
⚠️ Warning: Duplicate icon names detected after normalization:
"sun-moon" found in: [SunMoon, sun-moon, sunMoon, sun_moon]
These duplicates have been removed. Only the first occurrence is kept.
Organize icons using folders. The folder structure becomes the namespace:
Simple Namespace:
svg-icons/
└── social/
├── facebook.svg → social:facebook
└── twitter.svg → social:twitter
Nested Namespace:
svg-icons/
└── sidebar/
└── nav items/
└── user menu.svg → sidebar:nav-items:user-menu
Using Namespaced Icons:
<Icon name="social:facebook" size={24} />
<Icon name="sidebar:nav-items:user-menu" size={24} />Note: Folder names are also normalized to kebab-case, just like file names.
The scripts/build-sprite.js script:
- Reads all
.svgfiles from thesvg-icons/directory (including subdirectories) - Normalizes all file and folder names to kebab-case
- Groups icons by namespace based on folder structure
- Generates separate sprite files for each namespace:
- Root level icons →
icons-sprite.svg(default namespace) social/folder →icons-social.svgbrands/folder →icons-brands.svg
- Root level icons →
- Each icon becomes a
<symbol>with anid(without namespace prefix in the file) - Shows build errors when duplicate normalized names are detected
Example Build Output:
📦 Building sprite for namespace: brands
✓ brands:apple (from brands/apple)
✓ brands:google (from brands/google)
✅ Sprite saved: icons-brands.svg (2 icons)
📦 Building sprite for namespace: default
✓ home (from home)
✓ user (from user)
✅ Sprite saved: icons-sprite.svg (6 icons)
Default Route (app/icons/route.ts):
- Serves the default sprite at
/icons - Static generation with
force-static
Namespace Routes (app/icons/[namespace]/route.ts):
- Serves namespace sprites at
/icons/{namespace} - Static generation with
generateStaticParams() - Examples:
/icons/social,/icons/brands - All routes have proper caching headers for performance
The Icon component automatically:
- Detects namespace from icon name (e.g.,
social:facebook) - Loads from the correct sprite file:
<Icon name="home" />→ loads from/icons#home<Icon name="social:facebook" />→ loads from/icons/social#facebook
- Uses SVG
<use>to reference symbols - Provides props for easy customization (size, color, className)
Performance Benefits:
- Each page only loads the sprite files it needs
- Namespaced icons don't pollute the default sprite
- Better code splitting and faster page loads
No route setup required! Just copy the components and start using them:
-
Copy files:
components/IconDynamic.tsx- Dynamic icon componentcomponents/sprite-loader.ts- Sprite loader utility
-
Use in your project:
'use client'; import IconDynamic from '@/components/IconDynamic'; <IconDynamic name="home" size={24} />
-
That's it! Icons will be loaded dynamically with caching.
💡 Perfect for: Client-side apps, npm packages, projects where you want zero configuration
If you prefer static routes with SSG:
Copy these files to your project:
nextjs-svgsprite.js- Pluginscripts/build-sprite.js- Build scriptapp/icons/route.js- Route handlercomponents/Icon.jsx- Icon component
Update next.config.js:
const withSvgSprite = require('./nextjs-svgsprite');
const nextConfig = {
// Your existing config
};
module.exports = withSvgSprite()(nextConfig);Add scripts and dependencies:
{
"scripts": {
"build:sprite": "node scripts/build-sprite.js",
"build": "npm run build:sprite && next build"
},
"devDependencies": {
"svgstore": "^3.0.1"
}
}Create a svg-icons/ directory and add your SVG files.
npm install
npm run build:spriteIcons inherit the current text color by default. You can style them with CSS:
.my-icon {
color: blue;
transition: color 0.3s;
}
.my-icon:hover {
color: red;
}<Icon name="home" size={24} className="my-icon" />const icons = ['home', 'user', 'settings'];
{
icons.map((iconName) => <Icon key={iconName} name={iconName} size={24} />);
}<Icon
name="home"
size={24}
style={{
marginRight: '8px',
verticalAlign: 'middle',
}}
/>After running npm run build:sprite, you'll see output like:
Building SVG sprite...
Found 4 SVG file(s):
✓ home
✓ user
✓ settings
✓ search
Sprite generated successfully at: /path/to/public/icons-sprite.svg
Total icons: 4
- Check sprite generation: Run
npm run build:spritemanually - Verify route: Visit
/iconsin your browser to see if the sprite loads - Check icon names: Ensure the
nameprop matches the SVG filename (without .svg)
- Delete the old sprite: Remove
public/icons-sprite.svg - Rebuild: Run
npm run build:sprite - Clear Next.js cache: Delete
.next/directory and restart
- Check that your SVG files don't have hardcoded
fillorstrokeattributes - Use
currentColorin your SVG files for inheritable colors - The build script copies these attributes:
fill,stroke,stroke-width,stroke-linecap,stroke-linejoin
Before deploying:
- Run
npm run build:spriteto generate the sprite - Commit the sprite to git if needed (or regenerate during build)
- The
/iconsroute will serve the sprite with proper caching headers
Works in all modern browsers that support:
- SVG
<use>element - ES6+ JavaScript (via Next.js transpilation)
MIT
Feel free to submit issues and pull requests!