For an SSG that renders thousands of pages, or a web server handling concurrent icon lookups, the right provider and caching strategy make the difference between predictable performance and unexpected latency spikes.
Caching model
All four providers use an in-memory cache backed by sync.RWMutex. The read path acquires only a read lock, so concurrent reads do not contend. On a cache miss, a write lock is acquired, the icon is resolved, and the result is stored. Concurrent callers racing on the same name are safe: the first writer stores the result, and subsequent callers return the cached value.
| Provider | Parse cost | Cache scope | Warm-up |
|---|---|---|---|
DirectoryProvider |
Disk I/O + SVG parse per icon | Per-icon, lazy | Preload() |
JsonCollectionProvider |
JSON unmarshal (once) + per-icon resolution | sync.Once for parse, per-icon for resolution |
First access |
IconifyProvider |
HTTP request per icon | Per-icon, lazy | First access |
ChainProvider |
None (delegates) | None (delegates) | N/A |
DirectoryProvider
Each Get() call on a cache miss reads the SVG file from disk, parses it, sanitizes the content, and caches the result. Subsequent calls return the cached *Icon with no disk I/O.
Pre-warming with Preload
Call Preload() after construction to read all icons into the cache upfront:
import swarmicons "github.com/frostybee/go-swarm-icons"p, _ := swarmicons.NewDirectoryProvider("./icons")p.Preload()Preload walks the directory (respecting the recursive setting), calls Get() for each file, and populates the cache. After Preload, every subsequent Get() is a cache hit.
Use Preload in production deployments where the first-request latency of lazy loading is unacceptable.
JsonCollectionProvider
The JSON file (or embedded bytes) is parsed once on the first call to Get(), Has(), or All(), using sync.Once. This initial parse deserializes the entire JSON structure into memory. Subsequent calls skip parsing entirely.
Individual icons are resolved from the parsed data on first access (applying alias chains, transforms, and root-level dimension defaults) and cached per-name. The per-icon cache is separate from the parse step.
For large collections (thousands of icons), the first-access cost is dominated by the JSON unmarshal. Subsequent icon resolutions are fast map lookups.
IconifyProvider
Each Get() call on a cache miss sends an HTTP request to the Iconify API. The response is parsed, and the resolved icon is cached. Subsequent calls return the cached *Icon with no network I/O.
Failed fetches (network errors, non-200 responses, invalid JSON) are not cached. The provider retries the HTTP request on the next Get() call for that name.
Implications for build pipelines
Reserve IconifyProvider for development, prototyping, or applications where network access is acceptable.
ChainProvider
The chain provider has no cache of its own. Each Get() call delegates to the wrapped providers in order. The wrapped providers manage their own caches, so repeated calls through a chain still benefit from per-provider caching.
Icon immutability
Fluent methods on *Icon (Size, Class, Fill, Rotate, etc.) return new instances with deep-copied attribute maps. The cached icon is never mutated by caller transformations.
This means:
- Multiple goroutines can call
Get()for the same icon and apply different transformations concurrently. - The cached icon remains in its original state regardless of how many derived copies exist.
- No defensive copying is needed when passing icons between functions.
Recommendations
Static site generators (SSG)
- Use
DirectoryProviderorJsonCollectionProvider(or the embedded Lucide set) for deterministic, offline builds. - Call
Preload()on directory providers at startup to avoid lazy-load latency spikes during page rendering. - Avoid
IconifyProviderin CI/CD pipelines where network access may be restricted or unreliable.
Web servers
- All providers are safe for concurrent use. No additional synchronization is needed.
- The lazy-load model works well for web servers: the first request for each icon pays the resolution cost, and all subsequent requests are cache hits.
- For predictable latency, call
Preload()on directory providers during server startup.
High icon count
JsonCollectionProviderwith embedded bytes (NewJsonCollectionFromBytes) avoids disk I/O entirely. The JSON parse happens once, and all icons are resolved from memory.- For sets with thousands of icons where only a fraction are used, the lazy per-icon cache avoids resolving unused icons.
See also
- Thread Safety: concurrency model per component, including the
sync.RWMutexcaching pattern - Providers: comparison table and overview of all five provider types
- Cache Commands:
cache warmfor pre-fetching Iconify API responses before offline builds