How to publish a React component package, using NextJS as the compiler
This is a short guide on publishing a React component library package to NPM, using NextJS as the compiler.
Motivation
Your component may be a straight client component with client hooks, and as such doesn't fall in to the React Server Components behaviour.
However, there are two reasons we might still want to use NextJS as our base:
- Server side rendering will still occur for your component, essentially the first render of the component will be rendered server side.
- In the future you might want to include RSCs in your component library, and so it's easier to make it work now.
Example code
All code for this tutorial can be found in this repo.
Step 1: Scaffold your project using create-next-app
Run npx create-next-app
and follow the prompts
Step 2: Create a src/library
folder and create your components
This is where you will create your component(s) for publishing.
Everything else will still be a regular NextJS project. You can use this as a sandbox for development, it won't be packaged for export.
Step 3: Create a src/exports.ts
to export your files
1export * from "./library/MyComponent"
Step 4: Add a tsconfig.build.json
You now have two tsconfigs, one for the regular NextJS application, and one specifically for building the package for publishing.
1{
2 "compilerOptions": {
3 "target": "es5",
4 "lib": [
5 "dom",
6 "dom.iterable",
7 "esnext"
8 ],
9 "allowJs": true,
10 "skipLibCheck": true,
11 "esModuleInterop": true,
12 "allowSyntheticDefaultImports": true,
13 "strict": true,
14 "forceConsistentCasingInFileNames": true,
15 "noFallthroughCasesInSwitch": true,
16 "module": "CommonJS",
17 "moduleResolution": "node",
18 "resolveJsonModule": true,
19 "isolatedModules": true,
20 "jsx": "react-jsx",
21 "outDir": "dist",
22 "declaration": true
23 },
24 "include": [
25 "src/exports.ts",
26 ],
27
28}
29
Step 5: Add build and publish scripts to your package.json
At this step I also like to prefix the existing start
, build
scripts with next:
- because we need to distinguish between 'build the NextJS application' and 'build the package'.
9 "scripts": {
10 "next:dev": "next dev",
11 "next:build": "next build",
12 "next:start": "next start",
13 "next:lint": "next lint",
14 "build": "rm -rf dist && tsc -p tsconfig.build.json",
15 "prepublishOnly": "npm run build"
16 },
Step 6: Mark React a dev + peer dependency, Next a dev dependency in your package.json
The only items in your dependencies object should be things that are required for your package to run in the context it's installed.
By marking them as dev dependencies they're available to you while developing the package but won't be needlessly installed by the application that uses it.
17 "peerDependencies": {
18 "react": ">=18",
19 "react-dom": ">=18"
20 },
21 "devDependencies": {
22 "react": "^18",
23 "react-dom": "^18",
24 "next": "14.2.5",
25 "typescript": "^5",
26 "@types/node": "^20",
27 "@types/react": "^18",
28 "@types/react-dom": "^18",
29 "eslint": "^8",
30 "eslint-config-next": "14.2.5"
31 }
Note that I've marked the React peerDependencies with the >=18
rather than ^18
. With the recent stable release of React 19, we want our package to be usable with React 19 applications without showing this warning:
npm error Found: react@19.0.0
npm error node_modules/react
npm error react@"^19.0.0" from the root project
npm error
npm error Could not resolve dependency:
npm error peer react@"^18" from an-example-react-package-built-with-nextjs-tooling@0.3.0
npm error node_modules/an-example-react-package-built-with-nextjs-tooling
npm error an-example-react-package-built-with-nextjs-tooling@"*" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry
npm error this command with --force or --legacy-peer-deps
npm error to accept an incorrect (and potentially broken) dependency resolution.
Note that it's up to you to determine whether your package will be forward compatible with major React versions - for a simple package it probably will be.
Step 7: Add main
, files
and exports
properties to your package.json
We define the entry points for our package with the main
, exports
properties, as well as a files
property to only include the relevant code.
4 "main": "./dist/exports.js",
5 "files": ["./dist"],
6 "exports": {
7 ".": "./dist/exports.js"
8 },
The exports
syntax allows to define multiple entry points into our package, which as we'll see is necessary.
Step 8: Preview your package behaviour in another application using npm link
At this point it will be helpful to check if your package is building and configured correctly, and that another project is able to use it.
We can use npm link
to emulate using the package, without really publishing to NPM before we are ready.
npm run build
npm link
And in the other project
npm link an-example-react-package-built-with-nextjs-tooling
You should now be able to use the package as if it were any other package.
Step 9: Differentiating client and non-client components
If you have a client component like:
1"use client"
2import React, { useEffect } from "react";
3
4
5export function MyClientComponent() {
6
7 useEffect(() => {
8 console.log('hello!')
9 }, [])
10 return <div>This is the component</div>
11}
And an exports file like:
1export * from "./library/MyComponent"
2export * from "./library/MyClientComponent";
Then at this point you may be seeing this error when you go to use it:
Error: Unsupported Server Component type: undefined
This appears to be a pitfall with RSCs and NextJS see this Stack Overflow question.
The resolution is to export your RSCs or your client components separately, like:
1export * from "./library/MyComponent"
2
1"use client"
2export * from "./library/MyClientComponent";
and be sure to add this new exports file to your tsconfig.build.json
24 "include": [
25 "src/exports.ts",
26 "src/export-client.ts",
27 ],
We update our package.json to define a second entry point:
6 "exports": {
7 ".": "./dist/exports.js",
8 "./client": "./dist/export-client.js"
9 },
Now your users can use the components like so:
import {MyComponent } from "an-example-react-package-built-with-nextjs-tooling";
import {MyClientComponent} from "an-example-react-package-built-with-nextjs-tooling/client";
Step 9: npm publish
Run npm publish and see your published package!
This package is published here.
Addendum: Add other tooling you want to use
At this point you could add tools like Storybook for NextJS, or set up your unit tests etc.
Questions? Comments? Criticisms? Get in the comments! 👇
Spotted an error? Edit this page with Github