How we made registry metadata 70% smaller
Package managers download megabytes of package metadata on every clean install. Here is how vlt.io serves the same installs at a fraction of the size.

We've made vlt.io serve registry metadata 70% smaller when compared to npmjs.org. The smaller transfers (and other performance improvements we'll write about soon) add up to real time saved. This is especially important for developers (or agents!) who frequently perform clean installs in ephemeral environments.
Taking the median of five runs per registry, installs from vlt.io took 20–70% less time than from npmjs.org, depending on the package manager and project.
Next.js app (create-next-app):
| Package Manager | npmjs.org | vlt.io | % diff |
|---|---|---|---|
| npm | 19.0s | 13.8s | -27% |
| pnpm | 3.0s | 1.6s | -47% |
| bun | 1.8s | 1.4s | -22% |
| vlt | 11.8s | 7.3s | -38% |
| Package Manager | npmjs.org | vlt.io | % diff |
|---|---|---|---|
| npm | 11.6s | 4.8s | -58% |
| pnpm | 1.5s | 0.8s | -49% |
| bun | 1.0s | 0.6s | -37% |
| vlt | 9.8s | 2.8s | -72% |
We also publish continuous registry benchmarks that compare clean installs across npm-compatible registries.
And if you pay for bandwidth or egress on a hosted npm registry, like JFrog Artifactory or Cloudsmith, switching to vlt.io cuts your metadata egress by 70%, and your bill along with it.
What Is a Packument
A package.json is a manifest that describes a version of a package, including its dependencies, scripts, and other metadata. When you publish a new version of a package to an npm-compatible registry, the registry will take that new version and append it to the package's full metadata, which is called the packument. This packument contains all the individual manifests plus top-level metadata such as distribution tags and time of publication.
The problem is that packuments grow indefinitely as new versions are published. When there isn't a lockfile or a new package is being added, the package manager has to download the full packument to determine the correct versions to install. For these clean installs, packuments are a big slice of the bytes downloaded.
On a clean install from npmjs.org, packuments are about 45% of the bytes for npm, 45% for pnpm, 40% for vlt, and 25% for bun. The Next.js fixture alone is 458 packuments and 73.3 MB for npm, against 109.7 MB of tarballs. The largest single packuments (gzipped) are: @prisma/client 6.65 MB, prisma 5.40 MB, vite 4.41 MB, and playwright 3.41 MB.
What a Packument Is Used For
A package manifest serves several purposes, but we can generally classify fields as specifying runtime behaviors, specifying install-time behaviors, or neither.
Fields like exports are very important for how a package behaves once it is installed, but they have no effect during the installation process itself.
Every package manager reads the package.json inside the tarball once it's on disk, so trimming the packument changes nothing at runtime. All the fields published as part of the package.json are still available to your application's code.
Since a package manager only needs the install-time fields, the npm registry serves two forms of a packument: full and abbreviated. The problem is that the abbreviated packument is missing some fields and is only served when a client asks for it. The field list was set years ago and is hard to change. When libc started being used to pick native optional dependencies, it wasn't there, and minimumReleaseAge needs the time field for publish timestamps that aren't there either. The npm client itself doesn't use the abbreviated packument because it needs all these fields. Other package managers work around the issue by conditionally requesting the abbreviated packument when possible, but they still have to fall back to the larger packument when necessary.
On the vlt registry, we serve an install packument by default. It keeps the fields an install needs, including the ones the abbreviated form lacks like time, license and libc, and drops the per-version signature and checksum metadata from dist, keeping only integrity. If package maintainers require new fields in the future, we can quickly add them to every packument.
For vite@8.3.3, the latest version's entry in each form:
| Field | npm full | npm abbreviated | vlt default |
|---|---|---|---|
| name, version, engines, bin, dependencies, devDependencies, optionalDependencies, peerDependencies, peerDependenciesMeta, funding | ✓ | ✓ | ✓ |
| license, directories, scripts | ✓ | ✓ | |
| _npmUser | ✓ | ✓ (trust flag only) | |
| description, keywords, homepage, bugs, repository, author | ✓ | ||
| exports, imports, type | ✓ | ||
| _id, _hasShrinkwrap, _npmOperationalInternal | ✓ | ||
| dist.tarball, dist.integrity | ✓ | ✓ | ✓ |
| dist.attestations | ✓ | ✓ | ✓ (provenance flag only) |
| dist.signatures, dist.shasum | ✓ | ✓ | |
| dist.fileCount, dist.unpackedSize | ✓ | ✓ | |
| dist.alternates | ✓ | ||
| Whole packument, gzipped (759 versions) | 4.41 MB | 391 KB | 104 KB |
The fields we drop that the abbreviated form keeps are the signatures, the SHA-1 shasum and the attestation document: base64 and hex strings that gzip cannot compress. Removing them is what takes vite's packument from 391 KB to 104 KB on the wire. Installers verify tarballs against integrity, which we keep.
The Numbers
There are a lot of package managers and many ways for them to be configured. Each one makes slightly different decisions about when to request full versus abbreviated packuments.
- npm: Always requests the full packument.
- pnpm: Requests the abbreviated packument, but re-requests the full one for any package published inside its minimumReleaseAge window and for platform-specific optional dependencies. trustPolicy: no-downgrade makes it always request the full one.
- bun: Requests the abbreviated packument unless minimumReleaseAge is set, in which case it requests the full one.
- vlt: Requests our version of the abbreviated packument, which includes the time and libc fields, falling back to the full one. Registries that don't support this, like npmjs.org, respond with the full packument.
Full Packuments
Each row installs the same four projects (fixtures) from both registries with a cold cache and no lockfile. Sizes count packument bytes only, not tarballs.
| Package Manager | Command | npmjs.org transfer size | vlt.io transfer size | % diff |
|---|---|---|---|---|
| npm | npm install | 163.39 MB | 34.80 MB | -78.7% |
| bun | bun install --minimum-release-age 604800 (7 days) | 136.45 MB | 28.60 MB | -79.0% |
Abbreviated Packuments
pnpm and bun request abbreviated packuments by default, so the same four fixtures look like this:
| Package Manager | Command | npmjs.org transfer size | vlt.io transfer size | % diff |
|---|---|---|---|---|
| bun | bun install | 93.89 MB | 26.35 MB | -71.9% |
| pnpm | pnpm install (default minimumReleaseAge, 1 day) | 167.38 MB | 26.43 MB | -84.2% |
| pnpm | pnpm install with minimumReleaseAge: 10080 (7 days) | 192.66 MB | 26.43 MB | -86.3% |
Individual Packuments
The 1000 most downloaded packages on npm, plus a few individual packages:
| Package(s) | Full: npmjs.org | Full: vlt.io | % diff | Abbreviated: npmjs.org | Abbreviated: vlt.io | % diff |
|---|---|---|---|---|---|---|
| Top 1000 | 81.50 MB | 17.47 MB | -78.6% | 62.46 MB | 15.70 MB | -74.9% |
| prisma | 5.40 MB | 1.30 MB | -75.9% | 4.47 MB | 1.27 MB | -71.5% |
| @prisma/client | 6.65 MB | 1.77 MB | -73.4% | 5.76 MB | 1.71 MB | -70.4% |
| react | 1.31 MB | 0.28 MB | -78.4% | 1.12 MB | 0.28 MB | -75.3% |
pnpm
pnpm also fetches the full packument for any package with a version published inside the configured minimum release age. In our tests, the default 24-hour minimum release age caused 47 double fetches from npmjs.org, adding 66.05 MB of full packuments on top of the abbreviated ones. With a 7-day minimum release age, that grew to 91 double fetches and 91.32 MB. From vlt.io, there were zero double fetches in either case, with no extra configuration, because the abbreviated packument carries the time field.
Extras for the vlt Client
We've also added some optimizations when our vlt package manager talks to the vlt.io registry, further reducing the transfer size of packuments.
A common use case is for packages to publish prerelease versions for testing. Sometimes these prereleases are published automatically as nightly builds. Across the 1000 most downloaded packages, 58.6% of all published versions are prereleases. Developers install them far less often than stable versions. So in the vlt client and registry, we've added support for the ?stable query parameter, which allows clients to request only the stable versions of a package. vlt only adds it when the requested range can't match a prerelease, and only once the registry has advertised support for it.
Here is vlt install on the same four fixtures. npmjs.org serves full packuments, while vlt.io serves abbreviated packuments filtered with ?stable:
| Fixture | npmjs.org transfer size | vlt.io transfer size | % diff |
|---|---|---|---|
| Next.js app | 60.16 MB | 2.78 MB | -95.4% |
| Vite + React app | 38.74 MB | 1.63 MB | -95.8% |
| Express API | 20.98 MB | 1.33 MB | -93.7% |
| Node CLI | 16.70 MB | 1.75 MB | -89.5% |
| All four | 136.57 MB | 7.49 MB | -94.5% |
For individual packages, ?stable cuts the abbreviated packument down further by dropping prereleases:
| Package(s) | npmjs.org abbreviated | vlt.io abbreviated | vlt.io ?stable | Change vs npmjs.org | Versions (all → stable) |
|---|---|---|---|---|---|
| Top 1000 | 62.46 MB | 15.70 MB | 6.88 MB | -89.0% | 150,327 → 62,170 |
| prisma | 4.47 MB | 1.27 MB | 0.04 MB | -99.0% | 9354 → 317 |
| @prisma/client | 5.76 MB | 1.71 MB | 0.04 MB | -99.2% | 10658 → 201 |
| react | 1.12 MB | 0.28 MB | 0.01 MB | -98.8% | 2959 → 139 |
| next | 1.93 MB | 0.71 MB | 0.09 MB | -95.3% | 3973 → 419 |
| playwright | 3.02 MB | 0.55 MB | 0.02 MB | -99.4% | 5703 → 188 |
Share of a Clean Install
With each package manager's default configuration, packuments drop from 25–45% of a clean install's bytes on npmjs.org, as noted earlier, to 5–15% on vlt.io:
| Package Manager | npmjs.org | vlt.io |
|---|---|---|
| npm | 45% | 15% |
| pnpm | 45% | 12% |
| bun | 25% | 9% |
| vlt | 40% | 5% |
Methodology
You can find the source for all these numbers here: vltpkg/vlt-registry-packument-size. It runs each package manager through a local proxy while recording the transfer size before decompression. The transfer sizes are from a run on 2026-09-30 and the install times from 2026-10-06, with npm 12.1.0, pnpm 12.6.0, bun 1.4.2, and vlt 1.3.0.
You can run the benchmarks yourself by following the instructions in the repository. With a vlt.io account and token, you can also drop your own package.json into the fixtures to see how much bandwidth your projects would save.