lucide-react vs Heroicons vs Tabler Icons: the Next.js import surface, measured (2026)
Three React icon libraries that differ less in their icons than in their import surface. Read from the npm registry, the published tarballs, the GitHub API and the Next.js source on 4 October 2026: one of them has a 525-byte root entry, one ships a 478 KB barrel, and the registry's size figures rank all three in the wrong order.
On this page
Quick answer (October 2026). These three are not three flavours of the same thing. Measured live from the npm registry, the published tarballs, the GitHub API and the Next.js source on 4 October 2026: lucide-react and @tabler/icons-react each ship one enormous barrel module, 250,177 bytes with 1,870 exports and 477,855 bytes with 6,223 exports respectively. @heroicons/react has no root barrel at all. Its root entry is 525 bytes. The real entries are four per-style subpaths of roughly 21 KB each.
That single architectural difference decides most of the rest: how your bundler behaves on a cold start, what Next.js has to special-case on your behalf, and which of the three has a published style that Next.js quietly does not optimise.
One of these three is not shaped like the others
Resolve each package's real entry point from its own module or exports field rather than guessing at a filename, and the shapes separate immediately.
Scroll to see more
| Package | Entry resolved from | Entry size | Export statements |
|---|---|---|---|
| lucide-react 1.52.0 | module | 250,177 B | 1,870 (1,869 re-exports) |
| @tabler/icons-react 3.48.0 | module | 477,855 B | 6,223 |
| @heroicons/react 2.2.0 | exports["."] | 525 B | n/a |
Heroicons' 525-byte root is not a mistake and not a stub. That package is simply not designed to be imported from its root. Its exports map publishes four real entries instead, one per size and style, and those are the barrels that matter:
Scroll to see more
| Heroicons subpath | Barrel size | Exports |
|---|---|---|
@heroicons/react/24/outline | 21,737 B | 324 |
@heroicons/react/24/solid | 21,737 B | 324 |
@heroicons/react/20/solid | 21,737 B | 324 |
@heroicons/react/16/solid | 21,155 B | 316 |
So when you write import { HomeIcon } from "@heroicons/react/24/outline", a bundler reads about 21 KB and 324 re-export lines. When you write import { House } from "lucide-react", it reads 250 KB and 1,869 re-export lines, and for Tabler, 478 KB and 6,223. Every cold dev-server start, every production build, before any tree-shaking has removed a single byte.
This is the mechanism behind reports like the RedwoodJS issue Loading a lot of chunks when adding @tabler/icons to the project, filed in October 2024 and since closed, in which a single Tabler icon pulled in enough chunks to slow and occasionally crash the browser. The folklore that icon libraries make dev servers slow is not folklore. It is 6,223 modules sitting behind one import specifier.
What Next.js already does for you, and the one style it skips
Next.js carries a default optimizePackageImports list that rewrites barrel imports into direct module imports. All three libraries are on it, but not in the same way. Read from packages/next/src/server/config.ts in both canary and the current stable release:
Scroll to see more
| Entry in the default list | Form |
|---|---|
lucide-react | whole package |
@tabler/icons-react | whole package |
@heroicons/react/20/solid | one subpath |
@heroicons/react/24/solid | one subpath |
@heroicons/react/24/outline | one subpath |
Heroicons has to be enumerated subpath by subpath because, as a comment in that same file says, wildcard entries are not supported and the subpaths must be added by hand.
@heroicons/react/16/solid is not in that list. Zero occurrences, in canary and in stable. It is a fully supported, fully published style, declared in the package's own exports map and shipping 316 icon modules, and it is the one style that receives no barrel optimisation by default.
Before that becomes alarming, size it honestly. The un-optimised 16/solid barrel is 21,155 bytes and 316 re-exports. That is a fraction of what Lucide or Tabler would cost you if they fell off the list. The practical consequence is one line in your config, not a performance cliff:
// next.config.js
experimental: {
optimizePackageImports: ["@heroicons/react/16/solid"],
}
Worth knowing, worth one line, not worth panic. See the optimizePackageImports reference for the semantics.
Weight, and why npm ranks all three in the wrong order
The npm registry's unpackedSize is the number most comparisons reach for. On these three it is actively misleading, because it is dominated by things that never reach a browser.
Scroll to see more
| Package | unpackedSize | Shippable ESM | ESM share | Source maps | Type declarations |
|---|---|---|---|---|---|
| @tabler/icons-react | 66,405,078 B | 6,523,930 B | 9.8% | 48,657,822 B | 7,584,122 B in 1 file |
| lucide-react | 35,569,891 B | 2,029,543 B | 5.7% | 15,290,644 B | 11,827,191 B in 5 files |
| @heroicons/react | 3,688,492 B | 2,847,426 B | 77% | none | 833,248 B |
By unpackedSize, Tabler is eighteen times Heroicons. By actual shippable ESM it is 2.3 times, and Heroicons is larger than Lucide, which is the exact opposite of what the registry implies. Heroicons ships no source maps at all, which is why its install footprint is so much leaner than its code.
Two things fall out of that table that are easy to miss. Lucide ships 11.8 MB of type declarations across five files, and Tabler ships 7.6 MB in a single file. Those are not bundle costs, but they are real costs to your editor and to tsc, and nobody mentions them.
There is also a dependency worth reading properly. @tabler/icons-react pins a runtime dependency on @tabler/icons at an exact version, which looks like the usual wrapper-hiding-a-bigger-package pattern. It is not. That package contains 11,386 SVG files and zero JavaScript, with an exports map of {"./*": ["./icons/*"]}. It is an asset package. It contributes nothing to your bundle, and adding its 11.3 MB to Tabler's weight would be wrong.
Icon counts, counted from the artefacts
Scroll to see more
| Library | Icon modules | Unique designs |
|---|---|---|
| @tabler/icons-react | 6,221 | 6,221 |
| lucide-react | 2,130 | 2,130 |
| @heroicons/react | 1,288 | about 324 |
Heroicons' 1,288 is the number most roundups print, and it overstates the set by roughly four times. It is the same 324 designs drawn at 24px outline, 24px solid and 20px solid, plus 316 at 16px solid. If you need an icon that is not in that core 324, no amount of size variants will help.
The eight designs present at 24px but absent at 16px are ArrowLeftOnRectangleIcon, ArrowRightOnRectangleIcon, ArrowSmallDownIcon, ArrowSmallLeftIcon, ArrowSmallRightIcon, ArrowSmallUpIcon, MinusSmallIcon and PlusSmallIcon. Six of those are the Small variants, which are redundant at 16px by definition. That is a design decision, not a gap.
Maintenance, read properly
@heroicons/react 2.2.0 was published on 18 November 2024, 685 days before this was measured. That looks damning, and the obvious conclusion is wrong.
Read the default branch through the commits API rather than trusting the repository's pushed_at field, and master has had five commits in those 685 days: an OIDC setup change, one gift-icon alignment fix, two npm housekeeping commits and a semver bump for a vulnerability. There is no backlog of unreleased icons waiting behind a stalled release. The repository has 1 open issue and 3 open pull requests against 23,851 stars.
That is the signature of a project that is finished, not one that is abandoned. The honest cost is narrower and worth stating: one merged icon fix has sat unreleased since September 2025, and new icons are not arriving. If your product needs icons that do not exist yet, Heroicons is the wrong choice. If you need a stable set that matches Tailwind's design language, two years without a release is the feature.
Scroll to see more
| Repository | Stars | Open issues | Open PRs | Last default-branch commit |
|---|---|---|---|---|
| lucide-icons/lucide | 24,852 | 274 | 184 | 2026-10-03 |
| tailwindlabs/heroicons | 23,851 | 1 | 3 | 2026-05-12 |
| tabler/tabler-icons | 21,897 | 80 | 6 | 2026-10-04 |
Note that GitHub's open_issues_count includes pull requests, so the headline figures of 458, 4 and 86 need splitting before they mean anything.
The other two are both active and active in different ways. Lucide reached 1.0.0 on 23 March 2026 and has shipped 53 minor releases since, roughly one every three and a half days, the latest published the morning this was written. Its 75 branches are overwhelmingly human, with only six bot-owned, and recent commits come from many different contributors. Tabler's five most recent commits are all by Paweł Kuna and are all build infrastructure, and all 18 of its branches are human. That is a real bus-factor observation, not a criticism: the activity is intense and competent, it is simply concentrated in one person.
Licences, which are not all MIT
Heroicons and both Tabler packages are plain MIT. Lucide is not, and the detail matters if you run licence scanning.
lucide-react reports ISC in its package metadata, while GitHub's licence detector reports NOASSERTION. Reading the LICENSE file out of the published tarball resolves the disagreement: it is 3,208 bytes containing two complete licence bodies. An ISC licence for the project, then an unmodified MIT licence (Cole Bemis, 2013 onward) covering the 115 icons derived from the Feather project, which are listed by name.
Nothing there restricts use. The correct SPDX expression is ISC AND MIT, and a scanner reporting "unknown licence" for Lucide is a scanner artefact rather than a licence risk. It is still worth knowing before someone's compliance tooling flags it for you.
They are all Server Components
None of the three ships a "use client" directive, on the barrel or on individual icon modules. All three render as Server Components in the App Router with no client boundary. They are pure SVG with no hooks and no handlers. You only need a client component if you attach behaviour to one, and the icon will not force the boundary on you.
All three also declare sideEffects: false, which is the baseline that makes tree-shaking possible in the first place.
One genuine difference in escape hatches. Heroicons' exports map includes wildcard patterns such as ./24/outline/*, so import HomeIcon from "@heroicons/react/24/outline/HomeIcon" is a supported, declared, single-icon import. Lucide and Tabler publish no exports field at all, which means deep imports into their dist directories do work, but only as an accident of having no map. Those paths are undocumented internals and can move without a major version. If you plan to bypass the barrel by hand, only one of these three has promised you the path will still be there.
How to choose
Take @heroicons/react if you want the smallest and most predictable import surface, you are already on Tailwind, and about 324 designs covers you. It is the only one of the three whose architecture makes barrel cost a non-issue by default. Add @heroicons/react/16/solid to optimizePackageImports if you use that style. Accept that the set is finished.
Take lucide-react if you want breadth with the least friction. At 2,130 icons it is the middle option on size, it is on the Next.js default list as a whole package, and it is the most actively developed of the three. Its 129,748,621 weekly downloads are real but should be read carefully: it is the default icon dependency of shadcn/ui, so that figure measures shadcn adoption as much as deliberate choice.
Take @tabler/icons-react if you genuinely need the largest set. At 6,221 icons it is nearly three times Lucide and roughly nineteen times Heroicons' unique designs. You are accepting the largest barrel in the category and a project with a single primary maintainer.
The decision rule, stated plainly: choose on icon coverage first, because all three are free, MIT-compatible, tree-shakeable and Server Component safe. Then let the import surface decide your config, not your library.
If you are weighing build tooling around this, our TypeScript package bundler comparison covers how these barrels get produced in the first place, and the React list virtualization comparison deals with the other common source of large component trees.
Method
Every figure above was read from live state on 4 October 2026, not from secondary write-ups. Package metadata and versions from registry.npmjs.org. Download counts from the npm downloads API for the week of 23 to 29 September 2026. Entry points resolved from each package's own module and exports fields, then measured inside the published tarballs. Licences read from the LICENSE file in the tarball rather than from registry metadata, because the two disagree for Lucide. Icon counts taken by counting icon modules in the extracted artefacts. Repository figures from the GitHub API, with issues and pull requests separated through the search API, and the last commit read from the commits API on each default branch rather than from pushed_at, which bot activity keeps artificially fresh. The optimizePackageImports list was read from the Next.js source in both canary and the current stable tag. Vendor sites: lucide.dev, heroicons.com, tabler.io/icons.
Written by
Yui TanakaFrequently asked questions
Which React icon library has the smallest import surface?
@heroicons/react, by a wide margin. Measured on 4 October 2026, its root entry is 525 bytes because it is not designed to be imported from the root. Its real entries are four per-style subpaths of roughly 21 KB each, carrying 324 exports (316 at 16px). By comparison lucide-react ships a single 250,177 byte barrel with 1,870 exports, and @tabler/icons-react a 477,855 byte barrel with 6,223 exports.
Does Next.js optimize lucide-react, Heroicons and Tabler Icons by default?
All three appear in the default optimizePackageImports list, but not in the same form. lucide-react and @tabler/icons-react are listed as whole packages. Heroicons is listed as three enumerated subpaths: 20/solid, 24/solid and 24/outline. The fourth published style, @heroicons/react/16/solid, is absent from the list in both Next.js canary and the current stable release, so it receives no barrel optimisation unless you add it yourself. Its barrel is 21,155 bytes, so the practical fix is one config line rather than a performance problem.
How many icons does each library actually have?
Counted from the published artefacts: @tabler/icons-react ships 6,221 icon modules, lucide-react 2,130, and @heroicons/react 1,288 modules. The Heroicons figure overstates the set by about four times, because it is the same core of roughly 324 designs drawn at 24px outline, 24px solid and 20px solid, plus 316 at 16px solid.
Is Heroicons abandoned?
No. Version 2.2.0 was published on 18 November 2024, 685 days before measurement, which looks alarming until you read the default branch. It has had five commits in that period: an OIDC change, one icon alignment fix, two npm housekeeping commits and a security bump. There is no backlog of unreleased icons, and the repository carries 1 open issue and 3 open pull requests against 23,851 stars. That is a finished project rather than an abandoned one. The real cost is that one merged icon fix has been unreleased since September 2025 and new icons are not arriving.
Are these icon components safe to use in React Server Components?
Yes. None of the three ships a use client directive, on its barrel or on individual icon modules, so all three render as Server Components in the Next.js App Router with no client boundary. They are plain SVG with no hooks or handlers. You only need a client component if you attach behaviour yourself. All three also declare sideEffects false.
Is lucide-react MIT licensed?
Not exactly, and the distinction matters for licence scanning. lucide-react reports ISC in its package metadata while GitHub reports NOASSERTION. The LICENSE file in the published tarball is 3,208 bytes and contains two complete licence bodies: ISC for the project, plus an unmodified MIT licence covering the 115 icons derived from the Feather project. Nothing is restricted. The correct SPDX expression is ISC AND MIT. Heroicons and both Tabler packages are plain MIT.
More from the garden
tsup vs unbuild vs bunchee: TypeScript package bundlers in 2026
Three zero-config bundlers for shipping a TypeScript package, measured against live registry and repository state on 28 September 2026. One carries a maintainer deprecation notice, one has not released in over a year, and one requires Node 22.12. Plus what Vercel's turborepo actually uses, which is not what the usual citation says.
react-virtuoso vs TanStack Virtual vs react-window: the 2026 Next.js comparison
All three shipped a release in September 2026 and all three are MIT. What separates them is how much of the component they hand you: five prebuilt components, two, or none at all. The library everyone quotes as 5KB is measured here at eleven times that.
react-easy-crop vs react-image-crop vs react-advanced-cropper: choosing a Next.js image cropper in 2026
Three React image croppers whose received wisdom is three months out of date. Read from the npm registry, the published tarballs and the GitHub API on 1 October 2026: the library everyone calls selection-only now writes the canvas code for you, the most downloaded one still ships zero canvas code, and the package npm reports as smallest is actually the heaviest.