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.

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/web and 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 gen

Follow the prompts:

  1. Select "Create a new package"
  2. Enter the package name (e.g., analytics)
  3. Optionally add dependencies

The generator creates a package at packages/@kit/analytics with this structure:

packages/@kit/analytics/
├── src/
│ └── index.ts
├── package.json
└── tsconfig.json

Package.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 component
import { useAnalytics } from '@kit/analytics/client';
// In a server function or loader
import { 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:

  1. Transpiles the package (since it's TypeScript)
  2. Watches for changes and triggers HMR
  3. 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 zod

Using 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.ts

Run tests:

pnpm --filter @kit/analytics test

Example: Creating a Feature Package

Here's a complete example of creating a notifications package:

1. Generate

turbo gen
# Name: notifications

2. Structure

packages/@kit/notifications/
├── src/
│ ├── index.ts
│ ├── client.ts
│ ├── server.ts
│ ├── components/
│ │ └── notification-bell.tsx
│ ├── hooks/
│ │ └── use-notifications.ts
│ └── server/
│ └── send-notification.ts
└── package.json

3. 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 component
import { NotificationBell } from '@kit/notifications/client';
// Server function
import { sendNotification } from '@kit/notifications/server';

Troubleshooting

Module not found

  1. Ensure the package is matched by ssr.noExternal in apps/web/vite.config.ts
  2. Run pnpm install to link workspace packages
  3. 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.