Skip to content

Performance & weight ​

All numbers on this page are measured, not estimated, and you can reproduce them:

  • bundle costs with npm run bundle:budget (Vite probes built against the packed library). The tables below are rendered from its JSON output, docs/data/bundle-sizes.json;
  • file sizes and the comparison with node scripts/docs-compare.ts.

What it adds to your app ​

Each row is the difference against an empty app (React rows against an empty React app,

219.5 kB min / 67.7 kB gzip). Sizes are

min / gzip / brotli.

You useEntry JSLazy JS files ¹
pennants/art/1x1/ar + renderFlag (pennant)+4.6 kB / +1.7 kB / +1.6 kB0
pennants/art/4x3/ar + renderFlag (rect)+4.5 kB / +1.7 kB / +1.5 kB0
Both ar art sets + renderFlag (all four shapes)+7.0 kB / +2.0 kB / +1.8 kB0
getPennantName+1.8 kB / +1.0 kB / +785 B0
pennantUrl+1.7 kB / +899 B / +661 B0
fetchPennant (pennants/fetch)+4.1 kB / +2.1 kB / +1.7 kB0
loadPennant+5.1 kB / +2.5 kB / +2.1 kB544
loadFlag, circle + rect+5.1 kB / +2.5 kB / +2.1 kB544
pennants/element/register+11.9 kB / +5.2 kB / +4.4 kB545
React <Pennant>+10.0 kB / +4.7 kB / +3.9 kB545
React <Pennant shape="circle">+10.0 kB / +4.7 kB / +3.8 kB545
React <PennantSvg> + one art module+8.1 kB / +3.7 kB / +2.9 kB0
React <PennantSvg shape="circle"> + one art module+8.1 kB / +3.7 kB / +2.8 kB0

Vite 8.3.5 · min / gzip / brotli · 2026-10-22

¹ Emitted into your build, but downloaded only when needed: one chunk per flag and artwork set plus the loader maps. Fetch mode from pennants/fetch bundles its sanitizer and emits no lazy files.

Stylesheets ​

You useCSSLazy JS files ¹
CSS pennants/css, assetsInlineLimit set+15.7 kB / +3.8 kB / +2.8 kB0
CSS pennants/css/cdn+26.3 kB / +2.0 kB / +1.2 kB0
CSS pennants/css/rect, assetsInlineLimit set+17.0 kB / +3.8 kB / +2.8 kB0
CSS pennants/css/rect/cdn+26.7 kB / +1.9 kB / +1.1 kB0
CSS pennants/css/square, assetsInlineLimit set+17.5 kB / +3.8 kB / +2.8 kB0
CSS pennants/css/circle, assetsInlineLimit set+17.5 kB / +3.8 kB / +2.8 kB0
CSS pennants/css + pennants/css/circle on one page, assetsInlineLimit set+33.1 kB / +6.9 kB / +5.3 kB0
CSS pennants/css, Vite defaults (inlined flags)+376.2 kB / +53.0 kB / +43.8 kB0

Vite 8.3.5 · min / gzip / brotli · 2026-10-22

Each stylesheet also adds 27 bytes of entry JS. See FAQ: bundler asset imports for assetsInlineLimit; Next.js and Angular do not inline the flags.

What these numbers mean in practice:

  • A static artwork import plus renderFlag costs 1.7 kB gzip for the first flag; more shapes of the same flag reuse its artwork.
  • loadFlag and the components carry the loader maps up front (2.5 kB gzip for loadFlag) before any flag is shown. For a handful of known flags, static imports are cheaper; pennants/fetch (2.1 kB gzip, sanitizer included) ships no map at all. Fetch mode in <Pennant>, <pennant-flag> and the root fetchPennant loads the sanitizer as a lazy chunk on the first fetch.
  • In a Next.js Server Component, <PennantSvg> adds no client JavaScript: it renders to HTML.

Flag files ​

Measured on the 0.1.0 build with node scripts/docs-compare.ts (brotli is the sum of individually compressed files).

SetRawBrotli
All 271 pennant SVGs1.16 MB362 kB
All 271 rect / square / circle SVGs1.10 / 1.11 / 1.09 MB335 / 336 / 332 kB
Median SVG (pennant / rect)—525 B / 430 B
Largest SVG (rs, Serbia)62 kB15 kB
Sprite (sprite/pennant.svg)1.15 MB248 kB

PNG files average 0.9 kB (pennant 1x), 1.6 kB (pennant 2x) and 0.6–1.0 kB (other shapes, 1x only). Country names cost nothing: ISO names come from the runtime's Intl.DisplayNames; only the 22 non-ISO names (gb-eng, asean, ...) are bundled.

The npm tarball is 2.80 MB (9.29 MB unpacked) because it contains four shapes in every format. What reaches your users depends only on what you import.

How lazy chunks work ​

loadFlag holds a map of dynamic import() calls, one per flag and artwork set. Bundlers turn each entry into a separate chunk, so:

  • your build output contains one small file per flag and artwork set, plus the maps, but
  • a visitor downloads only the chunks for the flags actually shown, once, and the browser caches them.

<Pennant> and <pennant-flag> use the same loader and share an in-memory cache, so a flag shown many times is fetched and parsed once.

Keeping bundles minimal ​

  • Import what you use: import { getPennantName } from 'pennants' does not pull in any flag.
  • For a fixed, small set of flags, use static per-flag imports. Use loadFlag or the components when the flag is chosen at runtime and the set is large.
  • Avoid import * as flags or building an object of all flag modules: that bundles all 271.
  • Do not iterate codes and call loadFlag for each one to show a list; use CSS classes or <img> for long lists.
  • With Vite, SvelteKit or Astro, use a /cdn stylesheet or set assetsInlineLimit so the CSS does not inline every flag.

CDN or self-host? ​

jsDelivr (pennantUrl default)Self-hosted (base option)
SetupNoneCopy svg/ and png/ to your host
AvailabilityThird-party dependencySame as your site
PrivacyRequests go to jsDelivrNo third party
CSPAllow cdn.jsdelivr.net in img-srcimg-src 'self'
CachingVersioned URLs, cached long-termUp to your cache headers

pennantUrl defaults to a version-pinned base (DEFAULT_CDN_BASE), so URLs always match the installed version. Self-host with pennantUrl(code, { base: '/assets/pennants' }).

MIT licensed. Flag artwork from flag-icons (MIT).