DEV Community

Karthik Ng
Karthik Ng

Posted on

How I Accidentally Made My npm Package 324MB (and Fixed It)

Last week, I tried to create a CodeSandbox demo for my npm package. It failed. I tried to create a demo on https://playcode.io/typescript and it failed again.

The error message? ENOSPC: no space left on device

I stared at my screen, confused. Then I checked the package size.

324MB. ๐Ÿ˜ฑ

For context, React is about 6KB minified. My simple postal code lookup library was somehow 54,000 times larger.

Here's the story of how I accidentally shipped a massive package to npm, how I fixed it, and what I learned building zero-dependency lookup libraries that are now used by developers worldwide.


๐Ÿค” Why I Built This

It started with a simple need: I wanted to validate US ZIP codes in a React app.

"Easy," I thought. "There must be dozens of npm packages for this."

I was right - there were dozens. But each had issues:

โŒ Some only worked in Node.js - couldn't use them in React

โŒ Others required API calls - rate limits, network latency, offline failures

โŒ Many hadn't been updated in years - broken, outdated data

โŒ Several were huge - 50MB+ packages with tons of dependencies

โŒ Most lacked TypeScript support - no autocomplete, no type safety

I needed something that:

โœ… Works in browsers AND Node.js

โœ… Zero runtime dependencies

โœ… Full TypeScript support

โœ… Returns city, state, county, and coordinates

โœ… Actually maintained with fresh data

When I couldn't find it, I decided to build it myself.

Thus, zipcodes-us was born.

(And later, postalcodes-india for Indian postal codes)


๐Ÿ’ป What It Does

Here's the simplest example:

import zipcodes from 'zipcodes-us'

const info = zipcodes.find('90210')
console.log(info)
Enter fullscreen mode Exit fullscreen mode

Output:

{
  city: 'Beverly Hills',
  state: 'California',
  stateCode: 'CA',
  county: 'Los Angeles',
  latitude: 34.0901,
  longitude: -118.4065,
  isValid: true
}
Enter fullscreen mode Exit fullscreen mode

โ†’ Try it live in CodeSandbox

More Cool Features

Find all ZIP codes in a city:

const bostonZips = zipcodes.findByCity('Boston', 'MA')
// Returns array of all Boston ZIP codes with full info
Enter fullscreen mode Exit fullscreen mode

Search within a radius:

const nearby = zipcodes.findByRadius(37.7749, -122.4194, 10)
// Find all ZIP codes within 10 miles of San Francisco
Enter fullscreen mode Exit fullscreen mode

Auto-complete addresses:

// User enters ZIP code, auto-fill city and state
const { city, state } = zipcodes.find(userZipCode)
addressForm.city.value = city
addressForm.state.value = state
Enter fullscreen mode Exit fullscreen mode

And it works exactly the same in Node.js, React, Next.js, Vue, or vanilla JavaScript.


๐Ÿ› The 324MB Disaster

Everything was working beautifully locally. I ran my tests, they passed. I built the package, published v1.1.1 to npm, and went to create a demo.

I opened https://playcode.io/typescript, created a new project, added my package...

npm install postalcodes-india
Enter fullscreen mode Exit fullscreen mode

And then... nothing. The installation hung. Eventually:

Error: ENOSPC: no space left on device
Enter fullscreen mode Exit fullscreen mode

"What?" I thought. "It's just a postal code lookup. How much space could it possibly need?"

I checked unpkg.com to see what was actually published.

My package was 324MB. My US ZIP code package? 79MB.

For a simple lookup library with zero runtime dependencies.

๐Ÿ” Investigating the Problem

I dove into the published package contents:

dist/
  index.js          (6.9MB)
  index.js.map      (15.1MB)  โ† What are these?
  index.esm.js      (6.9MB)
  index.esm.js.map  (15.1MB)  โ† These look huge
  index.umd.js      (6.2MB)
  index.umd.js.map  (11.9MB)  โ† Oh no...
Enter fullscreen mode Exit fullscreen mode

The culprit: Source map files (.map files)

๐Ÿ˜ฑ What Are Source Maps?

Source maps are debugging tools that map minified/compiled code back to your original source code. They're super useful during development:

Without source maps:

Error at line 1: function a(b){return c(b)}
Enter fullscreen mode Exit fullscreen mode

(What does this mean?? ๐Ÿคท)

With source maps:

Error at line 42 in src/index.ts: 
  function findPostalCode(code) { 
    return lookup(code) 
  }
Enter fullscreen mode Exit fullscreen mode

(Ah, now I can debug it! โœ…)

๐Ÿคฆ The Problem

Source maps are typically 2-3x larger than the actual code. And I was:

  1. Generating them (fine for development)
  2. Publishing them to npm (not fine!)

My Rollup configuration looked like this:

export default [
  {
    input: 'src/index.ts',
    output: {
      file: 'dist/index.esm.js',
      format: 'es',
      sourcemap: true,  // โ† Creating massive .map files
    }
  },
  // ... similar for other builds
]
Enter fullscreen mode Exit fullscreen mode

That innocent sourcemap: true was creating huge .map files that were being published to npm, even though:

  • โŒ End users don't need them
  • โŒ They can't use them (they're for MY source code, not theirs)
  • โŒ They were making the package impossible to install in many environments

โœ… The Fix

The solution was surprisingly simple.

Step 1: Update Rollup Config

export default [
  {
    input: 'src/index.ts',
    output: {
      file: 'dist/index.esm.js',
      format: 'es',
      sourcemap: false,  // โ† Changed from true to false
    }
  },
  // ... same for all builds
]
Enter fullscreen mode Exit fullscreen mode

Step 2: Rebuild

npm run build
Enter fullscreen mode Exit fullscreen mode

I checked the dist/ folder - no more .map files! โœจ

Step 3: Publish

# Bump version
# Update package.json version to 1.1.2

npm publish
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“Š The Results

zipcodes-us:

  • Before: 79MB
  • After: 29.6MB
  • Reduction: 62.5% ๐ŸŽ‰

postalcodes-india:

  • Before: 324MB
  • After: 137MB
  • Reduction: 58% ๐ŸŽ‰

Now CodeSandbox could actually install them. Crisis averted!


๐ŸŽ“ What I Learned

1. Source Maps Are Not Needed in Production

Source maps are development tools. They help YOU debug YOUR code. End users installing your package:

  • Don't have access to your source files
  • Can't use the source maps anyway
  • Just want the compiled code to work

Lesson: Turn off source maps for npm packages (sourcemap: false).

Exception: Some argue you should ship source maps for debugging in production. If you do, consider:

  • Using .npmignore to exclude them
  • Or setting "files": ["dist/**/*.js", "!dist/**/*.map"] in package.json

2. Always Check Your Published Package

Before v1.1.2, I never actually looked at what was being published to npm. I assumed Rollup was doing the right thing.

Now I always check:

# See what will be published
npm pack --dry-run

# Or check on unpkg after publishing
https://unpkg.com/your-package@version/
Enter fullscreen mode Exit fullscreen mode

3. Test in Real Environments

My package worked perfectly on my local machine. But CodeSandbox, StackBlitz, and other online IDEs have disk space limits.

If your package is too big, it won't work in these environments - and developers LOVE trying packages in online editors before installing locally.

Lesson: Create a CodeSandbox demo as part of your development process, not after publishing.

4. Package Size Matters

I thought "it's just data, size doesn't matter." Wrong.

Large packages:

  • โŒ Take longer to install
  • โŒ Fill up disk space in CI/CD pipelines
  • โŒ Can't be used in constrained environments
  • โŒ Increase node_modules bloat
  • โŒ Make developers hesitant to use them

Lesson: Keep packages as small as possible. Every MB counts.


๐Ÿ—๏ธ Other Technical Challenges

Fixing the package size wasn't the only challenge. Here are other things I learned:

Making It Work Everywhere

The hardest part wasn't the lookup logic - it was making ONE package work in:

  • Node.js (CommonJS - require())
  • Node.js (ESM - import)
  • Browsers (via bundlers like Webpack/Vite)
  • Browsers (via CDN <script> tags)

The Solution: Multiple Build Targets

I use Rollup to create three different builds:

export default [
  // ESM for modern bundlers (Vite, Webpack 5)
  {
    input: 'src/index.ts',
    output: {
      format: 'es',
      file: 'dist/index.esm.js'
    }
  },

  // CommonJS for Node.js
  {
    input: 'src/index.ts',
    output: {
      format: 'cjs',
      file: 'dist/index.js',
      exports: 'named'
    }
  },

  // UMD for browsers (script tags)
  {
    input: 'src/index.ts',
    output: {
      format: 'umd',
      file: 'dist/index.umd.js',
      name: 'zipcodes'
    }
  }
]
Enter fullscreen mode Exit fullscreen mode

Then in package.json, I specify which file to use for each environment:

{
  "name": "zipcodes-us",
  "main": "dist/index.js",        // Node.js default (CommonJS)
  "module": "dist/index.esm.js",  // Modern bundlers (ESM)
  "browser": "dist/index.umd.js", // Browser <script> tag
  "types": "dist/index.d.ts"      // TypeScript definitions
}
Enter fullscreen mode Exit fullscreen mode

Now it just works, everywhere. Developers don't need to think about it.

Keeping Data Fresh

ZIP codes change! The USPS adds new ones, retires old ones, updates boundaries.

I couldn't just ship v1.0 and forget about it. I needed a system to keep data current.

My solution:

  1. Data comes from GeoNames (updated regularly, CC BY 4.0 license)
  2. I have a script which I run to check for updates. I try my best to check it daily.
  3. When changes are detected, I auto-generate a new data file
  4. Run tests to ensure nothing broke
  5. Publish a new version to npm

The data check script runs as a GitHub Action. Completely automated.

Lesson: If your package depends on external data, plan for updates from day one.


๐Ÿ“ˆ Results After Publishing the Fix

After fixing the package size and improving documentation:

zipcodes-us:

  • ๐Ÿ“ฆ 450+ weekly downloads
  • โญ 3 GitHub stars
  • ๐Ÿ› Zero open issues
  • ๐ŸŒ Used in production applications

postalcodes-india:

  • ๐Ÿ“ฆ 150+ weekly downloads
  • โญ 1 GitHub stars
  • ๐Ÿ‡ฎ๐Ÿ‡ณ Covers all 19,000+ Indian postal codes

Both packages are growing steadily. More importantly, they're actually useful to real developers.


๐ŸŽฏ What's Next

I'm working on:

  • Performance optimizations - can lookups be even faster?
  • More search methods - find by county, region, etc.
  • Better documentation - more examples and use cases
  • Maybe more countries? - depends on demand

The hardest part about maintaining open source isn't writing code - it's the ongoing commitment to:

  • Respond to issues quickly
  • Keep data updated
  • Write good documentation
  • Help users when they're stuck

But seeing my packages help other developers makes it worth it.


๐Ÿš€ Try It Yourself

If you need ZIP or postal code lookups in your project:

For US ZIP codes:

For Indian postal codes:

Installation is simple:

npm install zipcodes-us
# or
npm install postalcodes-india
Enter fullscreen mode Exit fullscreen mode

And if you find bugs or have feature requests, I actually respond to GitHub issues! ๐Ÿ˜„


๐Ÿ’ญ Your Turn

Have you ever shipped something way too big to npm? Made a similar mistake? I'd love to hear your stories in the comments!

And if you're building your own npm packages, here's my advice:

  1. โœ… Always check what you're actually publishing
  2. โœ… Test in constrained environments (CodeSandbox, etc.)
  3. โœ… Turn off source maps for production builds
  4. โœ… Keep package size as small as possible
  5. โœ… Provide TypeScript definitions (even if you write JS)
  6. โœ… Create live demos - developers love to try before installing

Happy coding! ๐ŸŽ‰

Top comments (0)