첫 npm 패키지를 배포했습니다 — 제가 한 모든 과정

발행: (2026년 6월 11일 AM 12:14 GMT+9)
8 분 소요
원문: Dev.to

출처: Dev.to

React 전자상거래 UI 키트인 Cartlify를 처음으로 npm에 배포하는 전체 과정을 단계별로 안내합니다.
어제 Cartlify를 npm에 배포했습니다.

npm install cartlify

간단해 보이지만, 그 한 줄에 도달하기까지 예상보다 많은 결정, 설정, 그리고 시행착오가 필요했습니다.
이 글에서는 빌드 설정부터 실제 배포 명령까지 모든 과정을 다루어, 여러분이 직접 고생하지 않도록 도와드립니다.

Cartlify는 생산 환경에 바로 사용할 수 있는 React + TypeScript + Tailwind CSS 기반 컴포넌트 라이브러리이며, 전자상거래 UI에 초점을 맞추었습니다.

모든 전자상거래 프로젝트에 필요한 4가지 컴포넌트

  • ProductCard — 3가지 레이아웃 변형, 이미지 갤러리, 위시리스트, 세일 배지, 스켈레톤 로딩
  • CartDrawer — 애니메이션 슬라이드‑인, 포커스 트랩, ESC 키로 닫기, 수량 스텝퍼
  • CheckoutStepper — 가로/세로, 애니메이션 커넥터, 키보드 네비게이션
  • PageLoader — 4가지 애니메이션 스타일, 3가지 위치 모드

그 외에도 3개의 유틸리티 훅, 11개의 트리‑쉐이킹 가능한 아이콘, 40개 이상의 CSS 디자인 토큰, 완전한 다크 모드, 그리고 141개의 Jest + React Testing Library 테스트가 포함되어 있습니다.

프리랜서 개발자와 인디 메이커가 번거로운 전자상거래 UI 레이어를 건너뛰고 더 빠르게 제품을 출시할 수 있도록 설계되었습니다.

배포 전까지 Cartlify는 Gumroad에서 유료 다운로드 형태로만 제공되었습니다.
이는 괜찮지만, npm을 통해 얻을 수 있는 신뢰도와 발견성은 Gumroad에서는 제공되지 않습니다.

  1. 개발자가 Cartlify를 확인한다 →
  2. npm install cartlify 로 설치한다 →
  3. 컴파일된 결과물을 평가한다 →
  4. 품질을 신뢰한다 →
  5. Gumroad에서 전체 소스 코드를 구매한다

npm은 단순한 배포 수단을 넘어, 패키지가 실제 존재하고 유지보수되며 생산 준비가 되었음을 알리는 신호 역할을 합니다. 또한 npmjs.com은 매달 수백만 건의 개발자 검색을 처리하므로, Gumroad만으로는 얻을 수 없는 무료 트래픽을 제공합니다.

배포 전 가장 중요한 결정: 라이브러리 번들링 방식

저는 esbuild 기반의 제로‑컨피그 TypeScript 번들러인 tsup을 선택했습니다. 이유는 다음과 같습니다.

도구필요 설정속도출력 형식
Rollup많음중간ESM + CJS
Webpack무겁다느리다CJS 전용
Vite lib mode약간빠름ESM + CJS
tsup거의 없음매우 빠름ESM + CJS + .d.ts

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

핵심 결정 사항

  • format: ['cjs', 'esm'] — 구형·신형 번들러 모두 지원
  • dts: true.d.ts 타입 정의 파일 자동 생성
  • minify: true — 배포용 파일을 압축 (Gumroad에서 제공하는 소스는 깔끔하게 유지)
  • sourcemap: false — 공개 npm 패키지에 소스맵 포함 안 함
  • external: ['react', 'react-dom'] — React 자체는 번들에 포함하지 않음

npm run build 실행 후 dist/ 폴더 구조는 다음과 같습니다.

dist/
├── index.js      ← CommonJS
├── index.mjs     ← ES Module
└── index.d.ts    ← TypeScript 타입 정의

npm 배포에 가장 중요한 package.json

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

반드시 올바르게 설정해야 할 필드

  • files — 배포에 필요한 파일만 포함
    "files": ["dist", "README.md"]
    이렇게 하면 src/, .storybook/, node_modules/, 테스트 파일 등이 npm 패키지에 포함되지 않습니다.
  • exports — 최신 번들러가 올바른 엔트리를 찾게 함
  • peerDependencies — React를 번들에 포함하지 않음
  • prepublishOnlynpm publish 전 자동으로 빌드·린트·테스트를 실행해 깨진 코드를 배포하는 실수를 방지

불필요한 파일을 배제하는 .npmignore 예시

src/
.storybook/
*.stories.tsx
*.test.tsx
*.test.ts
node_modules/
.eslintrc.json
.prettierrc
tsconfig.json
tailwind.config.js
tsup.config.ts
coverage/
.github/

.npmignore가 없으면 npm은 프로젝트 전체(소스, 테스트, 설정 파일 등)를 모두 배포하게 되어 패키지 크기가 불필요하게 커집니다.

경로 별칭(alias) 문제와 해결

개발 단계에서는 다음과 같이 별칭을 사용합니다.

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

하지만 npm용으로 빌드하면 별칭이 그대로 남아 최종 사용자가 오류를 겪게 됩니다.
tsup.config.ts에 아래와 같이 별칭을 해제하도록 설정하면, 컴파일 단계에서 실제 상대 경로로 변환됩니다.

esbuildOptions(options) {
  options.alias = {
    '@components': './src/components',
    '@primitives': './src/primitives',
    '@hooks': './src/hooks',
    '@utils': './src/utils',
    '@types': './src/types',
  };
},

npm 계정 만들고 배포하기

  1. npmjs.com → Sign Up
  2. 이메일 인증
  3. 2FA 활성화 (npm은 현재 배포 시 2FA를 필수로 요구)
  4. 터미널에서 로그인
    npm login
    브라우저가 열려 2FA를 확인합니다. 인증이 끝나면:
    npm whoami   # → yourusername

실제 배포 전 테스트

npm publish --dry-run

출력 예시:

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
0 조회
Back to Blog

관련 글

더 보기 »

Eidentic 소개

Today we're releasing Eidentic, an open-source TypeScript SDK for building AI agents with self-improving memory and the production fundamentals built in — not b...

Typescript의 타입

Introdução Tipos são uma forma de definir a “forma” ou o contrato dos dados que estamos usando no código. Pensando em Javascript puro, ele é dinâmico: você pode...