# I Just Published My First npm Package — Here's Everything I Did

Published: (June 10, 2026 at 11:14 AM EDT)
7 min read
Source: Dev.to

Source: Dev.to

A complete walkthrough of publishing Cartlify — a React e-commerce UI kit — to npm for the first time. Yesterday I published Cartlify to npm. npm install cartlify

It sounds simple. But getting to that one line took more decisions, more configuration, and more trial and error than I expected. This article covers everything — from setting up the build config to the actual publish command — so you don’t have to figure it out the hard way. Cartlify is a production-ready React + TypeScript + Tailwind CSS component library focused on e-commerce UI. 4 components that every e-commerce project needs: ProductCard — 3 layout variants, image gallery, wishlist, sale badges, skeleton loading CartDrawer — animated slide-in, focus trap, ESC dismiss, quantity stepper CheckoutStepper — horizontal/vertical, animated connectors, keyboard navigation PageLoader — 4 animation styles, 3 position modes Plus 3 utility hooks, 11 tree-shakeable icons, 40+ CSS design tokens, full dark mode, and 141 Jest + React Testing Library tests. Built so freelance developers and indie makers can skip the painful e-commerce UI layer and ship faster. Before npm, Cartlify was only available on Gumroad as a paid download. That’s fine — but npm adds something Gumroad can’t: Developer sees Cartlify → runs npm install cartlify → evaluates the compiled output → trusts the quality → buys the full source on Gumroad

npm is a credibility and discovery channel — not just a distribution method. A package on npm signals that something is real, maintained, and production-ready. Also: npmjs.com gets millions of developer searches every month. That’s free traffic you can’t get from Gumroad alone. The most important decision before publishing is how you bundle your library. I chose tsup — a zero-config TypeScript bundler built on esbuild. Here’s why:

Tool Config needed Speed Output

Rollup Lots Medium ESM + CJS

Webpack Heavy Slow CJS only

Vite lib mode Some Fast ESM + CJS

tsup Almost zero Very fast ESM + CJS + .d.ts

My tsup.config.ts: import { defineConfig } from ‘tsup’;

export default defineConfig({ entry: [‘src/index.ts’], format: [‘cjs’, ‘esm’], dts: true, sourcemap: false, clean: true, minify: true, external: [‘react’, ‘react-dom’], esbuildOptions(options) { options.alias = { ‘@components’: ’./src/components’, ‘@primitives’: ’./src/primitives’, ‘@hooks’: ’./src/hooks’, ‘@utils’: ’./src/utils’, ‘@types’: ’./src/types’, }; }, });

Key decisions: format: [‘cjs’, ‘esm’] — supports both older and modern bundlers dts: true — generates .d.ts TypeScript type definitions minify: true — compiled output is minified (buyers get clean source on Gumroad) sourcemap: false — no source maps in public npm package external: [‘react’, ‘react-dom’] — don’t bundle React itself After running npm run build, the dist/ folder looks like: dist/ ├── index.js ← CommonJS ├── index.mjs ← ES Module └── index.d.ts ← TypeScript types

This is the most critical file for npm publishing. Every field matters. { “name”: “cartlify”, “version”: “1.0.0”, “description”: “Production-ready React e-commerce UI kit — ProductCard, CartDrawer, CheckoutStepper, PageLoader. Built with TypeScript, Tailwind CSS and Storybook.”, “main”: ”./dist/index.js”, “module”: ”./dist/index.mjs”, “types”: ”./dist/index.d.ts”, “exports”: { ”.”: { “import”: ”./dist/index.mjs”, “require”: ”./dist/index.js”, “types”: ”./dist/index.d.ts” } }, “files”: [ “dist”, “README.md” ], “scripts”: { “build”: “tsup src/index.ts”, “prepublishOnly”: “npm run build && npm run lint && npm run test” }, “keywords”: [ “react”, “typescript”, “tailwind”, “ui-kit”, “ecommerce”, “cart”, “product-card”, “checkout”, “storybook”, “component-library”, “frontend” ], “author”: “Karthik G S ”, “license”: “MIT”, “homepage”: “https://cartlify.vercel.app”, “repository”: { “type”: “git”, “url”: “https://github.com/thirumalai77/cartlify” }, “bugs”: { “url”: “https://github.com/thirumalai77/cartlify/issues” }, “peerDependencies”: { “react”: ”>=18.0.0”, “react-dom”: ”>=18.0.0” }, “devDependencies”: { “tsup”: “^8.0.0”, “typescript”: “^5.0.0” } }

Key fields to get right: files — only publish what buyers need: “files”: [“dist”, “README.md”]

This stops src/, .storybook/, node_modules/, and test files from being included in the npm package. exports — modern bundler resolution: “exports”: { ”.”: { “import”: ”./dist/index.mjs”, “require”: ”./dist/index.js”, “types”: ”./dist/index.d.ts” } }

peerDependencies — don’t bundle React: “peerDependencies”: { “react”: ”>=18.0.0”, “react-dom”: ”>=18.0.0” }

prepublishOnly — runs before every publish automatically: “prepublishOnly”: “npm run build && npm run lint && npm run test”

This saved me from accidentally publishing broken code. Create this in your root to stop unnecessary files from being published: src/ .storybook/ *.stories.tsx *.test.tsx *.test.ts node_modules/ .eslintrc.json .prettierrc tsconfig.json tailwind.config.js tsup.config.ts coverage/ .github/

Without .npmignore, npm would publish your entire project including source code, test files, and config — making your package unnecessarily large. Cartlify uses path aliases to avoid ../../ imports: import { Button } from ‘@primitives/Button’; import { cn } from ‘@utils/cn’; import type { Product } from ‘@types’;

This works fine in development. But when you build for npm, the compiled output still has the alias references — and they break for the end user. The fix is in tsup.config.ts: esbuildOptions(options) { options.alias = { ‘@components’: ’./src/components’, ‘@primitives’: ’./src/primitives’, ‘@hooks’: ’./src/hooks’, ‘@utils’: ’./src/utils’, ‘@types’: ’./src/types’, }; },

This tells tsup to resolve aliases during compilation — the output dist/ files have no aliases, just clean relative paths. Go to npmjs.com → Sign Up Verify your email Enable 2FA — npm now requires this for publishing Login in terminal:

npm login

It opens a browser for 2FA confirmation. Once verified: npm whoami

→ yourusername

Before publishing for real: npm publish —dry-run

This shows exactly what will be uploaded without actually publishing: npm notice 📦 cartlify@1.0.0 npm notice === Tarball Contents === npm notice 2.1kB README.md npm notice 18.4kB dist/index.js npm notice 16.2kB dist/index.mjs npm notice 8.9kB dist/index.d.ts npm notice === Tarball Details === npm notice name: cartlify npm notice version: 1.0.0 npm notice filename: cartlify-1.0.0.tgz npm notice package size: 12.3 kB npm notice unpacked size: 45.6 kB npm notice total files: 4

Check two things: Only dist/ and README.md are listed ✅ No source files, test files, or config files ✅ npm publish —access public

Output: npm notice Publishing to https://registry.npmjs.org/

  • cartlify@1.0.0

That’s it. Live at npmjs.com/package/cartlify. After publishing, add these to your README.md: npm version npm downloads License: MIT

They render as clickable badges on GitHub — adds credibility instantly. Cartlify uses a split model: npm install cartlify (free) → Compiled dist/ output → ESM + CJS + TypeScript types → All 4 components usable

Gumroad ($29 one-time) → Full TypeScript source code → Storybook documentation → All hooks, icons, design tokens → README + usage examples → Future updates

The npm package gives developers everything they need to use Cartlify in a project. The Gumroad source lets them read, modify, and fully own the code. This is the same model used by shadcn/ui, Tailwind UI, and most successful component libraries — free to use, paid to own fully.

  1. tsup is the right tool for libraries.
  2. prepublishOnly is your safety net.
  3. Path aliases need special handling. esbuildOptions.alias config.
  4. Always dry run first. npm publish —dry-run saves you from publishing the wrong files. Run it every time.
  5. files in package.json matters.
  6. npm is a credibility signal. npm install cartlify

🔗 npm → npmjs.com/package/cartlify 🔗 GitHub → github.com/thirumalai77/cartlify 🔗 Live Storybook → cartlify.vercel.app 🛒 Full source on Gumroad ($29) → karthiksoftengg.gumroad.com/l/cartlify-react-ui-kit Now that Cartlify is on npm, next steps are: Product Hunt launch Grow npm downloads and Gumroad sales Write more tutorials around the components Will post a 30-day update with real numbers — downloads, views, and sales. If you’ve published an npm package before — what do you wish you’d known? Drop it in the comments. Built by Karthik G S — Senior Frontend Engineer with 10+ years in React, TypeScript, and React Native. Building Cartlify as an indie product alongside a full-time role. Tags: #react #typescript #npm #webdev #javascript #tutorial #showdev #indiehacker

0 views
Back to Blog

Related posts

Read more »