@tpzdsp/next-toolkit
v1.10.0
Published
A reusable React component library for Next.js applications
Readme
Next.js Component Library
A modern, reusable React component library designed specifically for Next.js applications. This library provides a collection of client and server components, hooks, contexts, and utilities with full TypeScript support.
📋 Prerequisites
- Node.js: 22.17.1 (managed with
asdf) - Package Manager: Yarn (preferred) or npm
- Next.js: 13+ (peer dependency)
- React: 18+ (peer dependency)
Features
- 🚀 Next.js Optimized: Built specifically for Next.js with support for both client and server components
- 🎨 Tailwind CSS v3: Styled with Tailwind CSS and includes
tailwind-mergefor efficient class merging - 📦 Multiple Export Patterns: Separate exports for client, server, and shared utilities
- 🧪 Fully Tested: Comprehensive test coverage with Vitest and React Testing Library
- 📝 TypeScript First: Full TypeScript support with proper type definitions
- ⚡ Fast Development: Built with Vite for lightning-fast development and builds
- 🎯 Tree Shakeable: Optimized for tree-shaking to reduce bundle size
- 🔄 Source Distribution: Distributes raw TypeScript files for optimal bundling
Installation
yarn add @your-org/nextjs-library
# or
npm install @your-org/nextjs-library
# or
pnpm add @your-org/nextjs-libraryUsage
Basic Import
// Import everything
import { Button, Card, useLocalStorage } from '@your-org/nextjs-library';
// Import styles (choose one method)
import '@your-org/nextjs-library/styles';
// OR if the above doesn't work:
// import '@your-org/nextjs-library/src/assets/styles/globals.css';
// If you're using map components, also import OpenLayers styles:
import '@your-org/nextjs-library/styles/ol';Selective Imports (Recommended)
// Import only client components
import { Button, NextLinkWrapper } from '@your-org/nextjs-library/client';
// Import only server components
import { Card, Container } from '@your-org/nextjs-library/server';
// Import components with heavy dependencies separately
import { Select } from '@your-org/nextjs-library/components/select';
// Import only types
import type { ButtonProps, ApiResponse, FormFieldProps } from '@your-org/nextjs-library/types';Example Usage
import { Button } from '@your-org/nextjs-library/client';
import { Card } from '@your-org/nextjs-library/server';
import '@your-org/nextjs-library/styles'; // Base styles
// Import map styles only if you're using map components:
// import '@your-org/nextjs-library/styles/ol';
export default function MyPage() {
return (
<Card title="Welcome" description="Get started with our components">
<Button variant="primary" size="lg">
Get Started
</Button>
</Card>
);
}Using Exported Types
import type {
ButtonProps,
ButtonVariant,
CardProps,
BaseProps,
ThemeContextValue,
} from '@your-org/nextjs-library';
// Use library types in your own components
interface MyCustomButtonProps extends ButtonProps {
icon?: React.ReactNode;
}
// Use type unions for consistency
const handleVariantChange = (variant: ButtonVariant) => {
console.log('Variant changed to:', variant);
};
// Extend base props for your own components
interface MyComponentProps extends BaseProps {
customProp: string;
}Available Components
Client Components
- Button: Interactive button with multiple variants and loading states
- NextLinkWrapper: Enhanced Next.js Link wrapper with external link handling
- ThemeProvider: Theme context provider for dark/light mode
Server Components
- Card: Flexible card component with title and description
- Container: Responsive container with multiple size options
Hooks
- useLocalStorage: localStorage hook with SSR support
- useDebounce: Debounce values and functions
Map Components
- Map: OpenLayers-based interactive map component
- MapContext: React context for map state management
- Popup: Map popup component for displaying information
- LayerSwitcherControl: Control for switching between map layers
Note: Map components require additional dependencies and styles:
# Install OpenLayers dependencies
npm install ol ol-geocoder ol-mapbox-style proj4// Import map styles when using map components
import '@your-org/nextjs-library/styles/ol';Components with Optional Dependencies
Some components require additional peer dependencies that are marked as optional:
- Select components: Require
react-select - Map components: Require OpenLayers packages (
ol,ol-geocoder, etc.)
# For Select components
npm install react-select
# For Map components
npm install ol ol-geocoder ol-mapbox-style proj4 @turf/turf geojson// Import Select components separately to avoid forcing dependency
import { Select } from '@your-org/nextjs-library/components/select';Utilities
- cn(): Tailwind class merging utility using tailwind-merge
- debounce(): Function debouncing utility
- formatBytes(): Human-readable byte formatting
Types & Interfaces
- Component Props:
ButtonProps,CardProps,ContainerProps, etc. - Common Types:
BaseProps,Variant,Size,Status,Theme - Form Types:
FormFieldProps,ChangeHandler,SubmitHandler - API Types:
ApiResponse<T>,PaginatedResponse<T>,PaginationMeta - Utility Types:
PropsWithRequired<T, K>,PropsWithOptional<T, K>
🏗️ Project Structure
src/
├── components/
│ ├── client/ # Client components ('use client' directive)
│ │ ├── Button/
│ │ │ ├── Button.tsx
│ │ │ └── Button.test.tsx
│ │ ├── NextLinkWrapper/
│ │ └── ThemeProvider/
│ └── server/ # Server components (SSR compatible)
│ ├── Card/
│ │ ├── Card.tsx
│ │ └── Card.test.tsx (when created)
│ └── Container/
├── hooks/ # React hooks (client-side only)
│ ├── useLocalStorage.ts
│ └── useDebounce.ts
├── contexts/ # React contexts (client-side only)
│ └── ThemeContext.tsx
├── utils/ # Utilities (client & server compatible)
│ └── index.ts
├── assets/ # CSS, images, and static assets
│ ├── styles/
│ └── images/
├── types.ts # TypeScript type definitions
└── index.ts # Main library exports
vitest.setup.ts # Test configuration (top-level)🧭 Architecture Decisions
Client vs Server Components
- Client Components (
src/components/client/): Include'use client';directive, used for interactive features, hooks, and state management - Server Components (
src/components/server/): No directive needed, can be server-side rendered, used for static content and layouts
Source Distribution Strategy
This library distributes raw TypeScript/TSX files instead of compiled bundles:
- ✅ Better tree-shaking in consuming applications
- ✅ Consuming apps control the build process and browser targets
- ✅ Smaller final bundle sizes
- ✅ No build step needed during development
Styling Approach
- Tailwind CSS v3 for utility-first styling
- tailwind-merge (
cn()utility) for intelligent class merging - Component-specific CSS classes in
assets/styles/globals.css
Development
# Install dependencies
yarn install
# Start development server
yarn dev
# Run tests
yarn test
# Run tests with UI
yarn test:ui
# Build the library (TypeScript check only)
yarn build
# Build distribution files (if needed)
yarn build:dist
# Preview the built library
yarn previewLocal Development with Yalc
This library works well with yalc for local testing before publishing to npm. However, hot reloading doesn't work reliably with yalc - you'll need to manually push changes and restart your development server.
Setup Yalc (one-time)
# Install yalc globally
npm install -g yalcPublishing to Yalc Store
# In this library directory
yalc publishUsing in Your Next.js App
# In your Next.js app directory
yalc add @tpzdsp/next-toolkit
# Install dependencies
yarn installYour Next.js App Configuration
Add to your next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ['@tpzdsp/next-toolkit'],
experimental: {
externalDir: true, // May help with yalc symlinks
},
};
module.exports = nextConfig;Development Workflow (Manual Process)
# 1. Make changes to the library components/hooks/etc.
# 2. Push changes to connected apps
yalc push
# 3. In your test app, clear Next.js cache and restart
rm -rf .next && yarn devWhy No Hot Reloading?
- Symlink Issues: Next.js file watching doesn't reliably detect changes in yalc-linked packages
- Module Resolution: TypeScript and bundler caching can prevent updates from being picked up
- Source Distribution: While our source distribution strategy helps with tree-shaking, it doesn't solve the hot reload problem with yalc
Removing from Test App
# In your Next.js app directory
yalc remove @tpzdsp/next-toolkit
yarn installBenefits Despite Manual Process
- ✅ Real Environment Testing: Test in an actual Next.js app setup
- ✅ Full TypeScript: Complete type checking and IntelliSense
- ✅ Tree Shaking: Your app's bundler handles optimization
- ✅ No Publishing: Test without polluting npm registry
- ❌ Manual Refresh Required: No automatic hot reloading
🛠️ Adding New Components
Client Component Template
// src/components/client/MyComponent/MyComponent.tsx
'use client';
import React from 'react';
import { cn } from '../../../utils';
export interface MyComponentProps {
children: React.ReactNode;
className?: string;
// Add your props here
}
export const MyComponent: React.FC<MyComponentProps> = ({ children, className, ...props }) => {
return (
<div className={cn('base-classes', className)} {...props}>
{children}
</div>
);
};Server Component Template
// src/components/server/MyComponent/MyComponent.tsx
import React from 'react';
import { cn } from '../../../utils';
export interface MyComponentProps {
children: React.ReactNode;
className?: string;
// Add your props here
}
export const MyComponent: React.FC<MyComponentProps> = ({ children, className, ...props }) => {
return (
<div className={cn('base-classes', className)} {...props}>
{children}
</div>
);
};Don't Forget To:
- Add exports to the appropriate
index.tsfile - Create tests co-located with your component (e.g.,
MyComponent.test.tsx) - Update TypeScript types if needed
- Test with
yalcin a real Next.js app
🐛 Troubleshooting
Common Issues
"Cannot resolve module" errors:
# Make sure transpilePackages is configured in next.config.js
transpilePackages: ['@your-org/nextjs-library']TypeScript errors in consuming app:
# Ensure your tsconfig.json includes:
{
"compilerOptions": {
"moduleResolution": "bundler", // or "node16"
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
}
}Tailwind styles not working:
# Import the library styles in your app:
import '@your-org/nextjs-library/styles';
# Or alternatively, import the CSS file directly:
import '@your-org/nextjs-library/src/assets/styles/globals.css';
# And include the library in your tailwind.config.js content:
content: [
// your paths...
'./node_modules/@your-org/nextjs-library/src/**/*.{js,ts,jsx,tsx}',
]yalc issues:
# Reset yalc completely
yalc remove --all
yalc publish
yalc add @your-org/nextjs-libraryOptional dependency errors:
# Error: Cannot resolve module 'react-select'
# Solution: Install the required dependency
npm install react-select
# Error: Cannot resolve module 'ol/ol.css'
# Solution: Install OpenLayers dependencies
npm install ol ol-geocoder ol-mapbox-style proj4
# Or import components selectively to avoid these dependencies:
import { Button, Card } from '@your-org/nextjs-library'; // ✅ No extra deps needed
import { Select } from '@your-org/nextjs-library/components/select'; // ❌ Requires react-selectTesting
This library uses Vitest and React Testing Library for testing:
# Run all tests
yarn test
# Run tests in watch mode
yarn test:watch
# Run tests with coverage
yarn test:coverageBuild Configuration
The library distributes raw TypeScript/TSX source files rather than pre-built bundles. This approach offers several benefits:
- Better Tree-Shaking: Your bundler can eliminate unused code more effectively
- Optimal Bundling: Your build process optimizes the code for your specific use case
- Smaller Bundle Size: No duplicate dependencies or unnecessary polyfills
- Modern Output: Your bundler can target your specific browser requirements
Requirements for Consuming Apps
Your Next.js application needs to be configured to transpile the library:
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ['@your-org/nextjs-library'],
};
module.exports = nextConfig;Tailwind CSS Integration
To use the library's Tailwind styles in your Next.js project:
- Import the library's Tailwind config:
// tailwind.config.js
import libraryConfig from '@your-org/nextjs-library/tailwind';
export default {
...libraryConfig,
content: [
...libraryConfig.content,
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
};- Import the library styles:
// pages/_app.js or app/layout.js or globals.css
import '@your-org/nextjs-library/styles';
// Alternative method (if the above doesn't work):
import '@your-org/nextjs-library/src/assets/styles/globals.css';Contributing
Getting Started
Install asdf and set Node.js version:
# Install asdf if you haven't already # Then install the required Node.js version asdf install nodejs 22.17.1 asdf local nodejs 22.17.1Install dependencies:
yarn installRun tests to ensure everything works:
yarn test
Development Workflow
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Make your changes following our component templates above
- Add tests for your changes (
src/test/YourComponent.test.tsx) - Run the test suite (
yarn test) - Test with yalc in a real Next.js app
- Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Code Style
- Use TypeScript for all files
- Follow the existing component patterns
- Use
cn()utility for className merging - Include proper TypeScript interfaces
- Add tests for new components/functions
- Document props and functions with JSDoc comments
License
MIT License - see the LICENSE file for details.
export default tseslint.config([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Remove tseslint.configs.recommended and replace with this
...tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
...tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
...tseslint.configs.stylisticTypeChecked,
// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
]);You can also install eslint-plugin-react-x and eslint-plugin-react-dom for React-specific lint rules:
// eslint.config.js
import reactX from 'eslint-plugin-react-x';
import reactDom from 'eslint-plugin-react-dom';
export default tseslint.config([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
]);