How to publish a React component to npm
Publishing a React component to npm is the gold standard for sharing reusable UI logic across projects or with the developer community. With over 25 years of experience in software development and as the creator of CoreUI, I’ve built and published hundreds of packages that power thousands of applications worldwide. The most efficient and modern approach involves bundling your component using a tool like Rollup to support both ESM and CommonJS formats while managing dependencies correctly. This ensures your library is lightweight, tree-shakeable, and compatible with various build environments like Vite, Next.js, or Webpack.
Bundle your code into a distributable format using Rollup or Microbundle and then run npm publish to share your component on the registry.
Setting Up the Project Structure
Before writing any code, you need a clean directory structure and a properly configured package.json. This file acts as the manifest for your library, defining its name, version, and entry points.
{
"name": "@your-username/my-react-component",
"version": "1.0.0",
"description": "A highly reusable React component",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"files": [
"dist"
],
"scripts": {
"build": "rollup -c",
"prepare": "npm run build"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@babel/preset-react": "^7.18.0",
"@rollup/plugin-babel": "^6.0.0",
"rollup": "^3.0.0",
"rollup-plugin-peer-deps-external": "^2.2.4"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
}
This configuration defines two main entry points: main for older environments using CommonJS and module for modern bundlers using ESM. The files array ensures that only the dist folder is uploaded to npm, keeping the package size small. We also list react as a peer dependency to avoid installing multiple versions of React in the consumer’s project.
Creating the React Component
Your source code should live in a dedicated directory, typically src. For this example, we will create a status badge component that utilizes standard React patterns.
import React from 'react'
const StatusBadge = ({ status, text }) => {
const styles = {
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px',
fontWeight: 'bold',
display: 'inline-block',
backgroundColor: status === 'active' ? '#4caf50' : '#f44336',
color: '#fff'
}
return (
<div style={styles}>
{text.toUpperCase()}
</div>
)
}
export default StatusBadge
Create an src/index.js file to serve as the entry point for your library. This re-exports the components you want to make available to consumers.
export { default as StatusBadge } from './StatusBadge'
This simple component demonstrates how to handle props and internal styling. In a real-world scenario, you might want to use a more robust styling solution. If you are building complex interfaces, consider exploring how we handle these patterns in our Free React Admin Template to see industry-standard implementations.
Configuring Rollup for Bundling
Rollup is the preferred bundler for libraries because it produces clean, flat bundles. Unlike Webpack, which is optimized for applications, Rollup focuses on creating efficient code for others to consume.
import { babel } from '@rollup/plugin-babel'
import peerDepsExternal from 'rollup-plugin-peer-deps-external'
export default {
input: 'src/index.js',
output: [
{
file: 'dist/index.cjs.js',
format: 'cjs',
sourcemap: true
},
{
file: 'dist/index.esm.js',
format: 'esm',
sourcemap: true
}
],
plugins: [
peerDepsExternal(),
babel({
babelHelpers: 'bundled',
exclude: 'node_modules/**',
presets: ['@babel/preset-env', '@babel/preset-react']
})
]
}
This configuration file tells Rollup to take your src/index.js and output it in two different formats. The peerDepsExternal plugin is crucial as it automatically prevents react and react-dom from being bundled into your library, which would cause runtime errors for the user.
Setting Up Babel
Since you are writing JSX and modern JavaScript, you need Babel to transpile your code into a format that works across different environments. Create a .babelrc file in your root directory.
{
"presets": [
[
"@babel/preset-env",
{
"modules": false
}
],
"@babel/preset-react"
]
}
Setting modules: false is important for Rollup because it allows Rollup to handle the module system itself, which enables better tree-shaking. The @babel/preset-react preset is what allows the compiler to understand the JSX syntax used in your components.
Managing Peer Dependencies
One of the most common mistakes when publishing a React library is including React in the dependencies list. This can lead to the “Invalid Hook Call” error if the consumer ends up with two versions of React.
{
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
},
"devDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
By placing React in peerDependencies, you are telling npm: “I need React to work, but I expect the host application to provide it.” You should still include it in devDependencies so that you can run tests and have IntelliSense working during development. For professional-grade components, you can see how we manage these dependencies in the CoreUI Badge documentation.
Authenticating and Publishing
Once your code is ready and the build script is tested, you need to authenticate with the npm registry and push your package.
# Login to your npm account
npm login
# Build the project to ensure dist/ is up to date
npm run build
# Publish the package
npm publish --access public
The npm login command will open a browser window for authentication. The npm run build command generates the dist folder. Finally, npm publish uploads your files. If you are using a scoped name (like @username/package), you must include the --access public flag unless you have a paid npm plan for private packages.
Testing the Published Component
After publishing, it is vital to verify that your component works as expected in a clean environment. You can use npm link during development, but a real installation test is better.
# In a new test project
npm install @your-username/my-react-component
# In your App.js
import MyComponent from '@your-username/my-react-component'
function App() {
return (
<MyComponent status='active' text='Online' />
)
}
If you need to perform string manipulations within your component, such as title casing props, you might find it useful to learn how to capitalize the first letter of a string in JavaScript to ensure your UI remains consistent.
Best Practice Note:
Always use a .npmignore file or the files field in package.json to exclude source code, tests, and configuration files from the final package. This reduces install time for your users and protects your development workflow. This is the same rigorous approach we use in CoreUI to maintain the performance and reliability of our enterprise-grade library.



