Add a Shared Package to Your Makerkit Monorepo
Create reusable packages for shared business logic, utilities, or components across your Turborepo monorepo applications.
Create shared packages in your Makerkit monorepo using turbo gen to scaffold a new package at packages/@kit/<name>. Shared packages let you reuse business logic, utilities, or components across multiple applications while maintaining a single source of truth.
When to Create a Package
Create a package when you have code that needs to be shared across multiple applications or when you want to enforce clear boundaries between different parts of your codebase. For code used only in apps/web, a folder within the app is simpler.
Create a Shared Package
Add a new package to your monorepo
When to Create a Package
Create a shared package when:
- Multiple applications: Code needs to be used across
apps/weband other applications - Clear boundaries: You want to enforce separation between different domains
- Reusable utilities: Generic utilities that could be used in any application
- Shared types: TypeScript types shared across the codebase
Keep code in apps/web when:
- It's only used in one application
- It's tightly coupled to specific routes or pages
- Creating a package adds complexity without benefit
Step 1: Generate the Package
Use the Turborepo generator to scaffold a new package:
turbo genFollow the prompts:
- Select "Create a new package"
- Enter the package name (e.g.,
analytics) - Optionally add dependencies
The generator creates a package at packages/@kit/analytics with this structure:
packages/@kit/analytics/├── src/│ └── index.ts├── package.json└── tsconfig.jsonPackage.json Structure
packages/@kit/analytics/package.json
{ "name": "@kit/analytics", "version": "0.0.1", "main": "./src/index.ts", "types": "./src/index.ts", "exports": { ".": "./src/index.ts" }, "scripts": { "typecheck": "tsc --noEmit" }, "devDependencies": { "typescript": "^5.9.0" }}Step 2: Configure Exports
Single Export (Simple)
For packages with a single entry point, export everything from index.ts:
packages/@kit/analytics/src/index.ts
export { trackEvent, trackPageView } from './tracking';export { AnalyticsProvider } from './provider';export type { AnalyticsEvent, AnalyticsConfig } from './types';Import in your application:
import { trackEvent, AnalyticsProvider } from '@kit/analytics';Multiple Exports (Tree-Shaking)
For packages with client and server code, use multiple exports for better tree-shaking:
packages/@kit/analytics/package.json
{ "name": "@kit/analytics", "exports": { ".": "./src/index.ts", "./client": "./src/client.ts", "./server": "./src/server.ts" }}Create separate entry points:
packages/@kit/analytics/src/client.ts
// Client-side analytics (runs in browser)export { useAnalytics } from './hooks/use-analytics';export { AnalyticsProvider } from './components/provider';packages/@kit/analytics/src/server.ts
// Server-side analytics (runs on server only)export { trackServerEvent } from './server/tracking';export { getAnalyticsClient } from './server/client';Import the specific export:
// In a client componentimport { useAnalytics } from '@kit/analytics/client';// In a server function or loaderimport { trackServerEvent } from '@kit/analytics/server';When to Use Multiple Exports
Use multiple exports when:
- Client/server separation: Code that should only run in one environment
- Large packages: Reduce bundle size by allowing apps to import only what they need
- Optional features: Features that not all consumers need
Step 3: Register in the Vite Config
Workspace packages ship TypeScript/TSX source, so the SSR build must bundle (not externalize) them. The apps/web/vite.config.ts ssr.noExternal option already matches every @kit/* package via a regex, so a package named @kit/analytics is picked up automatically:
apps/web/vite.config.ts
ssr: { // Bundle workspace packages (they ship TS/TSX) so the SSR runtime can // resolve and transpile them. noExternal: [ /^@kit\//, '@markdoc/markdoc', // ... other bundled deps ],},If you use a scope other than @kit, add its package name (or a matching pattern) to noExternal. This ensures Vite:
- Transpiles the package (since it's TypeScript)
- Watches for changes and triggers HMR
- Includes it in the SSR bundle
Step 4: Use the Package
Add as Dependency
Add the package to your application's dependencies:
apps/web/package.json
{ "dependencies": { "@kit/analytics": "workspace:*" }}Run pnpm install to link the workspace package.
Import and Use
apps/web/src/routes/__root.tsx
import { AnalyticsProvider } from '@kit/analytics';function RootDocument({ children }: { children: React.ReactNode }) { return ( <html> <body> <AnalyticsProvider>{children}</AnalyticsProvider> </body> </html> );}apps/web/src/routes/_authenticated/dashboard/index.tsx
import { createFileRoute } from '@tanstack/react-router';import { trackPageView } from '@kit/analytics';export const Route = createFileRoute('/_authenticated/dashboard/')({ component: HomePage,});function HomePage() { trackPageView({ page: 'home' }); return <div>Welcome</div>;}Package Development Patterns
Adding Dependencies
Add dependencies to your package:
pnpm --filter @kit/analytics add zodUsing Other Workspace Packages
Reference other workspace packages:
packages/@kit/analytics/package.json
{ "dependencies": { "@kit/shared": "workspace:*" }}TypeScript Configuration
The package's tsconfig.json should extend the root configuration:
packages/@kit/analytics/tsconfig.json
{ "extends": "../../../tsconfig.json", "compilerOptions": { "outDir": "./dist", "rootDir": "./src" }, "include": ["src/**/*"]}Testing Packages
Add tests alongside your package code:
packages/@kit/analytics/├── src/│ ├── index.ts│ └── tracking.ts└── tests/ └── tracking.test.tsRun tests:
pnpm --filter @kit/analytics testExample: Creating a Feature Package
Here's a complete example of creating a notifications package:
1. Generate
turbo gen# Name: notifications2. Structure
packages/@kit/notifications/├── src/│ ├── index.ts│ ├── client.ts│ ├── server.ts│ ├── components/│ │ └── notification-bell.tsx│ ├── hooks/│ │ └── use-notifications.ts│ └── server/│ └── send-notification.ts└── package.json3. Exports
packages/@kit/notifications/package.json
{ "name": "@kit/notifications", "exports": { ".": "./src/index.ts", "./client": "./src/client.ts", "./server": "./src/server.ts" }, "dependencies": { "@kit/supabase": "workspace:*" }}4. Implementation
packages/@kit/notifications/src/client.ts
export { NotificationBell } from './components/notification-bell';export { useNotifications } from './hooks/use-notifications';packages/@kit/notifications/src/server.ts
export { sendNotification } from './server/send-notification';5. Use
// Client componentimport { NotificationBell } from '@kit/notifications/client';// Server functionimport { sendNotification } from '@kit/notifications/server';Troubleshooting
Module not found
- Ensure the package is matched by
ssr.noExternalinapps/web/vite.config.ts - Run
pnpm installto link workspace packages - Check the export path matches your import
Types not resolving
Ensure tsconfig.json includes the package paths:
{ "compilerOptions": { "paths": { "@kit/*": ["./packages/@kit/*/src"] } }}HMR not working
Verify the package is matched by ssr.noExternal in apps/web/vite.config.ts and restart the dev server.
Related Resources
- Adding Turborepo Apps for creating new applications