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

작성자

카테고리:

← 피드로
DEV Community · Karthik Gs · 2026-06-11 개발(SW)

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

The Milestone

Yesterday I published Cartlify to npm.

npm install cartlify

Enter fullscreen mode Exit fullscreen mode

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.

What Is Cartlify?

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.

Why Publish to npm?

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

Enter fullscreen mode Exit fullscreen mode

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 Build Setup — tsup

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',
    };
  },
});

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

The package.json Setup

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 <[email protected]>",
  "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"
  }
}

Enter fullscreen mode Exit fullscreen mode

Key fields to get right:

files — only publish what buyers need:

"files": ["dist", "README.md"]

Enter fullscreen mode Exit fullscreen mode

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"
  }
}

Enter fullscreen mode Exit fullscreen mode

peerDependencies — don’t bundle React:

"peerDependencies": {
  "react": ">=18.0.0",
  "react-dom": ">=18.0.0"
}

Enter fullscreen mode Exit fullscreen mode

prepublishOnly — runs before every publish automatically:

"prepublishOnly": "npm run build && npm run lint && npm run test"

Enter fullscreen mode Exit fullscreen mode

This saved me from accidentally publishing broken code.

The .npmignore File

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/

Enter fullscreen mode Exit fullscreen mode

Without .npmignore, npm would publish your entire project including source code, test files, and config — making your package unnecessarily large.

Path Aliases — The Tricky Part

Cartlify uses path aliases to avoid ../../ imports:

import { Button } from '@primitives/Button';
import { cn } from '@utils/cn';
import type { Product } from '@types';

Enter fullscreen mode Exit fullscreen mode

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',
  };
},

Enter fullscreen mode Exit fullscreen mode

This tells tsup to resolve aliases during compilation — the output dist/ files have no aliases, just clean relative paths.

Creating the npm Account

  1. Go to npmjs.com → Sign Up
  2. Verify your email
  3. Enable 2FA — npm now requires this for publishing
  4. Login in terminal:
npm login

Enter fullscreen mode Exit fullscreen mode

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

npm whoami
# → yourusername

Enter fullscreen mode Exit fullscreen mode

The Dry Run — Always Do This First

Before publishing for real:

npm publish --dry-run

Enter fullscreen mode Exit fullscreen mode

This shows exactly what will be uploaded without actually publishing:

npm notice 📦  [email protected]
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

Enter fullscreen mode Exit fullscreen mode

Check two things:

  • Only dist/ and README.md are listed ✅
  • No source files, test files, or config files ✅

The Publish Command

npm publish --access public

Enter fullscreen mode Exit fullscreen mode

Output:

npm notice Publishing to https://registry.npmjs.org/
+ [email protected]

Enter fullscreen mode Exit fullscreen mode

That’s it. Live at npmjs.com/package/cartlify.

Adding npm Badges to README

After publishing, add these to your README.md:

[![npm version](https://badge.fury.io/js/cartlify.svg)](https://npmjs.com/package/cartlify)
[![npm downloads](https://img.shields.io/npm/dm/cartlify.svg)](https://npmjs.com/package/cartlify)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Enter fullscreen mode Exit fullscreen mode

They render as clickable badges on GitHub — adds credibility instantly.

The Free npm + Paid Source Model

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

Enter fullscreen mode Exit fullscreen mode

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.

What I Learned

1. tsup is the right tool for libraries.
Zero config, fast, outputs exactly what npm needs. Don’t overthink the build setup.

2. prepublishOnly is your safety net.
It runs your build, lint, and tests automatically before every publish. You can’t accidentally publish broken code.

3. Path aliases need special handling.
They work in development but break in compiled output without the 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.
Without it, npm publishes your entire project. Specify exactly what should ship.

6. npm is a credibility signal.
A package on npm feels more legitimate than a Gumroad ZIP alone. Developers trust it more.

Try Cartlify

npm install cartlify

Enter fullscreen mode Exit fullscreen mode

🔗 npmnpmjs.com/package/cartlify

🔗 GitHubgithub.com/thirumalai77/cartlify

🔗 Live Storybookcartlify.vercel.app

🛒 Full source on Gumroad ($29)karthiksoftengg.gumroad.com/l/cartlify-react-ui-kit

What’s Next

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

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다