App-Scoped Feature Packages
Use a single app-scoped feature package when functionality is tightly coupled to one app. Avoid micro-packages like kanban-core and kanban-fs; instead expose intent-based surfaces.
Standard Pattern
- Package name:
@repo/app-<app>(e.g.,@repo/app-kanban) - Surfaces:
./server,./web,./types(optional./client) - Internals: Organize freely (
server/{core,fs,...}) but keep them private - Exports: types from
src/*, runtime fromdist/*(compiled package pattern) - No
process.envin packages; follow environment getter and lazy init rules
Example
// Server code
import { loadKanbanTasks } from "@repo/app-kanban/server";
// Client component
"use client";
import { sortTasksByPriority } from "@repo/app-kanban/web";
// Shared contracts
import type { KanbanTask } from "@repo/app-kanban/types";
Tier-Based Pattern
When an app has distinct feature tiers (free/paid), use tier-based subpaths instead of runtime-based surfaces:
- Package name:
@repo/app-<app>(e.g.,@repo/app-library) - Surfaces:
./shared,./free,./paid - Shared: Types, transformers, mocks used by all tiers
- Free/Paid: Tier-specific UI components
Example: @repo/app-library
// Free tier page - gets free components + shared
import { FlowsListView, VideoInfo } from "@repo/app-library/free";
// Paid tier page - gets paid components + shared
import { VideosDashboard, VideoInfo } from "@repo/app-library/paid";
// Just shared utilities
import { toVideoInfo, mockFlowInfos } from "@repo/app-library/shared";
Folder Structure
packages/app-library/
├── src/
│ ├── index.ts # Barrel for shared only
│ ├── shared/ # Types, transformers, mocks
│ │ ├── index.ts
│ │ ├── types.ts
│ │ ├── transformers.ts
│ │ └── mocks.ts
│ ├── free/ # Free tier components
│ │ ├── index.ts
│ │ └── [components].tsx
│ └── paid/ # Paid tier components
│ ├── index.ts
│ └── [components].tsx
When to Use Tier Pattern
Use tier-based organization when:
- App has distinct pricing/access tiers
- Components clearly belong to one tier
- Tiers share common types and utilities
Use standard pattern (server/web/types) when:
- Separation is by runtime environment
- No pricing/access tier distinction
- Components shared across app contexts
See Demo App and Tier Architecture for complete details.
Promotion Path
If another app needs the feature, rename the package to @repo/<feature> and keep surfaces stable to minimize churn.
Next.js
List app-scoped packages in transpilePackages of the consuming app.