Team Workspaces
Create isolated development environments for teams within the monorepo — each team gets dedicated apps, packages, and infrastructure, while sharing common code and maintaining clear boundaries.
Know your team structure?
Why team workspaces matter
Without team isolation in monorepos:
- Code ownership chaos — Unclear who maintains what, conflicting changes to shared code
- Dependency conflicts — Teams step on each other's toes, breaking builds across the monorepo
- Deployment complexity — All teams deploy together, can't ship independently
- Permission nightmares — No granular access control, everyone touches everything
- Onboarding confusion — New developers don't know where their team's code lives
- Merge conflicts — Multiple teams editing the same files causes constant conflicts
Team workspaces with proper isolation — Dedicated directory structure (teams/team-name/{apps,packages,infra}),
team-scoped package naming (@team-name/*), clear dependency rules (can use @repo/*, not other teams), independent
deployment, and workspace filtering by team — ensures teams work independently while sharing infrastructure.
Production-ready with 3 active team workspaces (AI, iStudio, Engineering), 5 personal workspaces for
experimentation, shared access to 35+ @repo/* packages, independent CI/CD per team, and clear promotion path from team
to shared packages.
Use cases
Use team workspaces to:
- Isolate team development — Teams work on features without affecting others
- Manage ownership — Clear responsibility for apps, packages, infrastructure
- Ship independently — Deploy team apps without waiting for other teams
- Experiment safely — Prototype new ideas without breaking shared code
- Scale organizations — Add new teams without restructuring the entire monorepo
- Control access — Granular permissions per team workspace in CI/CD
Quick Start
Essential steps
Set up a team workspace in 3 steps:
1. Create team directory structure
# Create team workspace
mkdir -p teams/myteam/{apps,packages,infra}
# Create first team app
mkdir -p teams/myteam/apps/myteam-dashboard/src/app2. Configure team app with Next.js 16
// teams/myteam/apps/myteam-dashboard/package.json
{
"name": "myteam-dashboard",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "next dev --port 3100",
"build": "next build",
"start": "next start",
"lint": "eslint src/",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@repo/ui": "workspace:*",
"@repo/utils": "workspace:*",
"next": "^16.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@repo/config": "workspace:*",
"typescript": "^5.0.0"
}
}3. Install and run team app
# Install dependencies
pnpm install
# Run team app
pnpm --filter=myteam-dashboard devThat's it! Your team workspace is ready. Team app runs on port 3100, isolated from other teams.
Workspace structure
Complete team directory layout
Every team workspace follows this structure:
teams/myteam/
├── apps/ # Team applications
│ ├── myteam-dashboard/ # Main team app
│ │ ├── src/
│ │ ├── package.json # name: "myteam-dashboard"
│ │ ├── tsconfig.json
│ │ ├── eslint.config.mjs
│ │ └── next.config.ts
│ └── myteam-mobile/ # Team mobile app
│ ├── app/
│ ├── package.json # name: "myteam-mobile"
│ └── app.json
├── packages/ # Team-specific packages
│ ├── myteam-utils/ # Shared team utilities
│ │ ├── src/
│ │ ├── package.json # name: "@myteam/utils"
│ │ └── tsconfig.json
│ └── myteam-components/ # Team UI components
│ ├── src/
│ └── package.json # name: "@myteam/components"
└── infra/ # Infrastructure code
├── terraform/ # Infrastructure as code
├── kubernetes/ # K8s manifests
├── scripts/ # Deployment scripts
└── docker/ # DockerfilesActive team workspaces
AI Team (teams/ai/):
- Apps: AI model training dashboards, experiment tracking
- Packages: ML utilities, model wrappers
- Infra: GPU cluster configs, training pipelines
iStudio Team (teams/istudio/):
- Apps: Content creation tools, media management
- Packages: Video processing, image optimization
- Infra: Media storage, CDN configuration
Engineering Team (teams/engineering/):
- Apps: Developer tools, monitoring dashboards
- Packages: Build utilities, testing helpers
- Infra: CI/CD configs, deployment automation
Personal workspaces
For individual experimentation:
personal/
├── andy/
│ ├── apps/experiment-app/
│ └── packages/prototype-lib/
├── torin/
│ ├── apps/demo-app/
│ └── packages/test-utils/
├── michael/
├── vinay/
└── jeff/Use for:
- Learning new technologies
- Prototyping features before team implementation
- Personal tools and scripts
- Experimenting without breaking anything
Creating team apps
Next.js 16 app setup
Complete package.json:
{
"name": "myteam-dashboard",
"version": "0.0.0",
"private": true,
"description": "Team dashboard for monitoring and analytics",
"scripts": {
"dev": "next dev --port 3100",
"build": "next build",
"start": "next start --port 3100",
"lint": "eslint src/",
"typecheck": "tsc --noEmit",
"test": "vitest run"
},
"dependencies": {
"@repo/ui": "workspace:*",
"@repo/utils": "workspace:*",
"@repo/auth": "workspace:*",
"@repo/db-prisma": "workspace:*",
"next": "^16.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@repo/config": "workspace:*",
"@types/node": "^22.0.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"typescript": "^5.0.0",
"vitest": "^4.0.0"
}
}tsconfig.json:
{
"extends": "@repo/config/typescript/nextjs.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"#/*": ["./src/*"]
},
"plugins": [
{
"name": "next"
}
]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules", ".next", "out"]
}eslint.config.mjs:
import nextConfig from "@repo/config/eslint/next";
export default [...nextConfig];next.config.ts:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
reactStrictMode: true,
transpilePackages: ["@repo/ui", "@repo/utils"],
experimental: {
typedRoutes: true
}
};
export default nextConfig;Create app page:
// src/app/page.tsx
import { Button } from "@repo/ui";
export default function Page(): JSX.Element {
return (
<div className="p-8">
<h1 className="text-3xl font-bold">My Team Dashboard</h1>
<Button variant="primary">Get Started</Button>
);
}Run the app:
pnpm --filter=myteam-dashboard devThat's it! Your team app is running on http://localhost:3100.
React Native app setup
For mobile team apps:
{
"name": "myteam-mobile",
"version": "0.0.0",
"private": true,
"main": "expo-router",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"lint": "eslint app/",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@repo/uni-ui": "workspace:*",
"@repo/utils": "workspace:*",
"expo": "^52.0.0",
"expo-router": "^4.0.0",
"react-native": "^0.76.0"
},
"devDependencies": {
"@repo/config": "workspace:*",
"typescript": "^5.0.0"
}
}tsconfig.json:
{
"extends": "@repo/config/typescript/react-native.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"#/*": ["./app/*"]
}
}
}eslint.config.mjs:
import expoConfig from "@repo/config/eslint/expo";
export default [...expoConfig];That's it! Your mobile app uses shared @repo/uni-ui components compatible with web and native.
Creating team packages
Team utility package
For code shared within the team:
# Create package
mkdir -p teams/myteam/packages/myteam-utils/srcpackage.json:
{
"name": "@myteam/utils",
"version": "0.0.0",
"private": true,
"description": "Shared utilities for myteam",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"lint": "eslint src/",
"typecheck": "tsc --noEmit",
"test": "vitest run"
},
"dependencies": {
"@repo/types": "workspace:*",
"zod": "^4.0.0"
},
"devDependencies": {
"@repo/config": "workspace:*",
"typescript": "^5.0.0",
"vitest": "^4.0.0"
}
}tsconfig.json:
{
"extends": "@repo/config/typescript/node.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}Create utility:
// src/index.ts
import type { Brand } from "@repo/types";
export type TeamId = Brand<string, "TeamId">;
/**
* Formats team metrics for display.
*
* @param metrics - Raw metrics data
* @returns Formatted metrics object
*/
export function formatTeamMetrics(metrics: { totalUsers: number; activeUsers: number; revenue: number }): {
totalUsers: string;
activeUsers: string;
revenue: string;
conversionRate: string;
} {
return {
totalUsers: metrics.totalUsers.toLocaleString(),
activeUsers: metrics.activeUsers.toLocaleString(),
revenue: `$${(metrics.revenue / 100).toFixed(2)}`,
conversionRate: `${((metrics.activeUsers / metrics.totalUsers) * 100).toFixed(1)}%`
};
}Use in team app:
# Add to team app
pnpm --filter=myteam-dashboard add @myteam/utils// In team app
import { formatTeamMetrics } from "@myteam/utils";
const formatted = formatTeamMetrics({
totalUsers: 1000,
activeUsers: 750,
revenue: 50000
});That's it! Team package is ready to use across team apps.
Team component package
For team-specific UI components:
{
"name": "@myteam/components",
"version": "0.0.0",
"private": true,
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts",
"./TeamCard": "./src/TeamCard.tsx"
},
"dependencies": {
"@repo/ui": "workspace:*",
"clsx": "^2.1.0"
},
"devDependencies": {
"@repo/config": "workspace:*",
"@types/react": "^19.0.0",
"typescript": "^5.0.0"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}Create component:
// src/TeamCard.tsx
import { Button } from "@repo/ui";
export interface TeamCardProps {
title: string;
description: string;
onAction?: () => void;
}
export function TeamCard({ title, description, onAction }: TeamCardProps): JSX.Element {
return (
<div className="rounded-lg border p-6 shadow-sm">
<h3 className="text-xl font-semibold">{title}</h3>
<p className="mt-2 text-gray-600">{description}</p>
{onAction && (
<Button variant="primary" onClick={onAction} className="mt-4">
View Details
</Button>
)}
);
}Export from index:
// src/index.ts
export { TeamCard } from "./TeamCard";
export type { TeamCardProps } from "./TeamCard";That's it! Team components are ready to use, building on shared @repo/ui.
Dependency management
Dependency rules
Teams CAN use:
- ✅ All shared packages (
@repo/*) — Authentication, UI, database, utilities - ✅ Own team packages (
@myteam/*) — Team-specific utilities and components - ✅ External npm packages — Any public npm package
Teams SHOULD AVOID:
- ❌ Other team packages (
@otherteam/*) — Creates tight coupling between teams - ❌ Direct imports from other teams' apps — Breaks isolation
Correct dependencies:
// ✅ Good - using shared packages
import { Button } from "@repo/ui";
import { formatDate } from "@repo/utils";
import { db } from "@repo/db-prisma";
// ✅ Good - using own team packages
import { formatTeamMetrics } from "@myteam/utils";
import { TeamCard } from "@myteam/components";
// ✅ Good - external npm packages
import { z } from "zod";
import { clsx } from "clsx";
// ❌ Avoid - cross-team dependency
import { something } from "@ai/ml-utils"; // Don't use other team packages
// ❌ Avoid - direct app imports
import { config } from "../../../istudio/apps/studio/config"; // Never do thisWhen to use other team packages
Only when:
- Teams explicitly coordinate and agree
- Package is stable and won't change frequently
- Clear ownership and maintenance plan
Better approach:
Promote the package to packages/ as @repo/* so all teams can use it.
Promoting packages to shared
When to promote:
- Package is used by 2+ teams
- Package provides broadly useful functionality
- Package is stable and well-tested
How to promote:
# 1. Move package
mv teams/myteam/packages/useful-utils packages/useful-utils
# 2. Update package.json name
# "@myteam/useful-utils" → "@repo/useful-utils"
# 3. Update imports in all team apps
# Find all usages
grep -r "@myteam/useful-utils" teams/
# 4. Update to new name
# "@myteam/useful-utils" → "@repo/useful-utils"
# 5. Reinstall dependencies
pnpm install
# 6. Create changeset
pnpm changeset
# Summary: "Promote @myteam/useful-utils to @repo/useful-utils"That's it! Package is now available to all teams.
Running team commands
Filter by team workspace
# Run dev for all team apps
pnpm --filter="./teams/myteam/apps/*" dev
# Build all team packages
pnpm --filter="./teams/myteam/packages/*" build
# Lint everything in team workspace
pnpm --filter="./teams/myteam/**" lint
# Type check all team code
pnpm --filter="./teams/myteam/**" typecheckFilter specific team app
# Run specific app
pnpm --filter=myteam-dashboard dev
# Build specific app
pnpm --filter=myteam-dashboard build
# Test specific app
pnpm --filter=myteam-dashboard testAdd convenience scripts
In root package.json:
{
"scripts": {
"dev:myteam": "pnpm --filter=./teams/myteam/apps/* dev",
"build:myteam": "pnpm --filter=./teams/myteam/** build",
"lint:myteam": "pnpm --filter=./teams/myteam/** lint",
"test:myteam": "pnpm --filter=./teams/myteam/** test",
"dev:ai": "pnpm --filter=./teams/ai/apps/* dev",
"build:ai": "pnpm --filter=./teams/ai/** build",
"dev:istudio": "pnpm --filter=./teams/istudio/apps/* dev",
"build:istudio": "pnpm --filter=./teams/istudio/** build"
}
}Run with:
# Development
pnpm dev:myteam
pnpm dev:ai
pnpm dev:istudio
# Build
pnpm build:myteam
pnpm build:aiThat's it! Easy commands for running team workspaces.
Infrastructure setup
Team infrastructure directory
Not managed by pnpm — purely for infrastructure code:
teams/myteam/infra/
├── terraform/
│ ├── main.tf # Terraform configuration
│ ├── variables.tf
│ └── outputs.tf
├── kubernetes/
│ ├── deployment.yaml # K8s manifests
│ ├── service.yaml
│ └── ingress.yaml
├── scripts/
│ ├── deploy.sh # Deployment scripts
│ ├── rollback.sh
│ └── migrate.sh
└── docker/
├── Dockerfile # Docker images
└── docker-compose.ymlTerraform example
# teams/myteam/infra/terraform/main.tf
terraform {
required_providers {
vercel = {
source = "vercel/vercel"
version = "~> 1.0"
}
}
}
resource "vercel_project" "myteam_dashboard" {
name = "myteam-dashboard"
framework = "nextjs"
git_repository = {
type = "github"
repo = "your-org/monorepo"
}
environment = [
{
key = "DATABASE_URL"
value = var.database_url
target = ["production"]
}
]
root_directory = "teams/myteam/apps/myteam-dashboard"
}Kubernetes example
# teams/myteam/infra/kubernetes/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myteam-dashboard
namespace: myteam
spec:
replicas: 3
selector:
matchLabels:
app: myteam-dashboard
template:
metadata:
labels:
app: myteam-dashboard
spec:
containers:
- name: dashboard
image: ghcr.io/your-org/myteam-dashboard:latest
ports:
- containerPort: 3000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: myteam-secrets
key: database-urlDeployment script example
#!/bin/bash
# teams/myteam/infra/scripts/deploy.sh
set -e
echo "Deploying myteam-dashboard..."
# Build app
pnpm --filter=myteam-dashboard build
# Deploy to Vercel
vercel --prod --cwd teams/myteam/apps/myteam-dashboard
echo "✅ Deployment complete!"That's it! Team infrastructure is isolated and manageable.
Best practices
1. Keep team boundaries clear
Don't reach into other teams' code:
// ❌ Bad - reaching into another team's code
import { helper } from "../../../ai/packages/utils/src/helper";
// ✅ Good - use shared packages
import { helper } from "@repo/utils";
// ✅ Good - coordinate with other team
// If they promote their package to @repo/*2. Use shared packages first
Before creating team package, check if shared package exists:
# Check existing packages
ls packages/
# If exists, use it
pnpm --filter=myteam-dashboard add @repo/existing-utils
# Only create team package if truly team-specific3. Promote reusable code early
If you find yourself duplicating code:
# Instead of copying utils to multiple team apps
# Move to team package
mkdir -p teams/myteam/packages/shared-utils
# If multiple teams need it
# Promote to @repo/*
mv teams/myteam/packages/shared-utils packages/shared-utils4. Document team conventions
Create team README:
# teams/myteam/README.md
## My Team Workspace
Team workspace for the MyTeam product.
### Apps
- **myteam-dashboard** - Main dashboard application
- **myteam-mobile** - Mobile app
### Packages
- **@myteam/utils** - Shared team utilities
- **@myteam/components** - Team-specific UI components
### Running locally
\`\`\`bash pnpm dev:myteam \`\`\`
### Deployment
See `infra/scripts/deploy.sh` for deployment process.
### Team conventions
- Use `@repo/ui` for standard components
- Create team components in `@myteam/components` only for team-specific UI
- All API routes should use AsyncResult pattern
- Follow shared coding conventions5. Use consistent naming
Clear ownership in names:
✅ Good naming:
- teams/ai/apps/ai-training-dashboard
- teams/ai/packages/ai-ml-utils
- teams/istudio/apps/istudio-editor
- teams/istudio/packages/istudio-media
❌ Bad naming:
- teams/ai/apps/dashboard (too generic)
- teams/ai/packages/utils (conflicts with @repo/utils)
- teams/istudio/apps/app (meaningless)6. Independent CI/CD per team
GitHub Actions workflow per team:
# .github/workflows/myteam-ci.yml
name: MyTeam CI
on:
push:
branches: [main, develop]
paths:
- "teams/myteam/**"
pull_request:
paths:
- "teams/myteam/**"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- run: pnpm install
- run: pnpm --filter=./teams/myteam/** lint
- run: pnpm --filter=./teams/myteam/** typecheck
- run: pnpm --filter=./teams/myteam/** buildThat's it! Team CI/CD runs only when team code changes.
Troubleshooting
Team package not found
Error:
Cannot find module '@myteam/utils' or its corresponding type declarationsSolution:
# 1. Verify package exists
ls teams/myteam/packages/
# 2. Install dependencies
pnpm install
# 3. Add to team app if needed
pnpm --filter=myteam-dashboard add @myteam/utils
# 4. Verify workspace configuration
# Check pnpm-workspace.yaml includes:
# - 'teams/*/apps/*'
# - 'teams/*/packages/*'Cross-team dependency error
Error:
Package @ai/ml-utils not found in workspaceSolution:
// Don't use other team packages directly
// Instead:
// 1. Option A: Ask them to promote to @repo/*
// 2. Option B: Duplicate the functionality in your team package
// 3. Option C: Move to shared package yourself
// For now, remove the import:
// import { something } from "@ai/ml-utils"; // ❌
// Use shared package instead:
import { something } from "@repo/utils"; // ✅Port conflict between team apps
Error:
Port 3000 is already in useSolution:
// Assign unique ports per team app
// teams/myteam/apps/myteam-dashboard/package.json
{
"scripts": {
"dev": "next dev --port 3100" // Unique port
}
}
// teams/ai/apps/ai-dashboard/package.json
{
"scripts": {
"dev": "next dev --port 3200" // Different port
}
}Port allocation guide:
3000-3099: Shared apps3100-3199: MyTeam apps3200-3299: AI team apps3300-3399: iStudio team apps3400-3499: Engineering team apps3500+: Personal workspace apps
Next steps
- Create packages: Creating Packages →
- Understand architecture: Workspace Architecture →
- Manage dependencies: Dependency Management →
- Follow conventions: Coding Conventions →
- Set up deployment: Platform Apps →
For Developers: Advanced team workspace patterns
Advanced workspace patterns
Shared team infrastructure
For infrastructure used across team apps:
teams/myteam/infra/shared/
├── database/
│ ├── schema.prisma # Shared database schema
│ └── migrations/
├── api/
│ ├── openapi.yaml # API specification
│ └── postman/
└── configs/
├── env.example # Environment variables template
└── secrets.template.yamlTeam-specific environment configuration
Create team environment template:
# teams/myteam/.env.example
# Database
DATABASE_URL="postgresql://user:pass@localhost:5432/myteam_db"
# Authentication
AUTH_SECRET="your-secret-here"
AUTH_URL="http://localhost:3100"
# External APIs
TEAM_API_KEY="your-api-key"
TEAM_API_URL="https://api.example.com"
# Feature flags
ENABLE_TEAM_FEATURE_X="true"Load in team apps:
// teams/myteam/apps/myteam-dashboard/src/env.ts
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
AUTH_SECRET: z.string().min(1),
TEAM_API_KEY: z.string().min(1)
},
client: {
NEXT_PUBLIC_TEAM_NAME: z.string().min(1)
},
experimental__runtimeEnv: {
NEXT_PUBLIC_TEAM_NAME: process.env.NEXT_PUBLIC_TEAM_NAME
},
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
emptyStringAsUndefined: true
});Monorepo-wide team registry
Track all teams in one place:
// packages/team-registry/src/index.ts
export const TEAMS = {
ai: {
id: "ai",
name: "AI Team",
apps: ["ai-training-dashboard", "ai-experiment-tracker"],
packages: ["@ai/ml-utils", "@ai/model-wrappers"],
portRange: [3200, 3299],
owner: "ai-team@company.com"
},
istudio: {
id: "istudio",
name: "iStudio Team",
apps: ["istudio-editor", "istudio-viewer"],
packages: ["@istudio/media", "@istudio/components"],
portRange: [3300, 3399],
owner: "istudio-team@company.com"
},
myteam: {
id: "myteam",
name: "My Team",
apps: ["myteam-dashboard", "myteam-mobile"],
packages: ["@myteam/utils", "@myteam/components"],
portRange: [3100, 3199],
owner: "myteam@company.com"
}
} as const;
export type TeamId = keyof typeof TEAMS;
export function getTeamById(id: TeamId) {
return TEAMS[id];
}Cross-team shared packages
When multiple teams need similar functionality:
packages/team-commons/ # Shared team utilities
├── src/
│ ├── analytics/ # Shared analytics setup
│ ├── auth/ # Shared auth patterns
│ └── notifications/ # Shared notification system
└── package.json # @repo/team-commonsTeams import:
import { setupAnalytics } from "@repo/team-commons/analytics";
import { withAuth } from "@repo/team-commons/auth";Team-specific testing strategies
Shared test utilities per team:
// teams/myteam/packages/test-utils/src/index.ts
import { render as rtlRender } from "@testing-library/react";
import type { ReactElement } from "react";
// Team-specific test wrapper
export function render(ui: ReactElement) {
return rtlRender(ui, {
wrapper: ({ children }) => (
<TeamProvider teamId="myteam">
<AuthProvider>
{children}
</AuthProvider>
</TeamProvider>
),
});
}
// Team-specific test data factories
export function createTeamUser(overrides = {}) {
return {
id: "user_123",
teamId: "myteam",
email: "user@myteam.com",
role: "member",
...overrides,
};
}Use in team tests:
// teams/myteam/apps/myteam-dashboard/src/components/Dashboard.test.tsx
import { render, createTeamUser } from "@myteam/test-utils";
import { Dashboard } from "./Dashboard";
test("renders dashboard for team user", () => {
const user = createTeamUser({ role: "admin" });
const { getByText } = render(<Dashboard user={user} />);
expect(getByText("Team Dashboard")).toBeInTheDocument();
});Team-specific code generators
Plop generators for team code:
// teams/myteam/plopfile.js
export default function (plop) {
// Generate team component
plop.setGenerator("component", {
description: "Create a team component",
prompts: [
{
type: "input",
name: "name",
message: "Component name:"
}
],
actions: [
{
type: "add",
path: "packages/myteam-components/src/{{pascalCase name}}.tsx",
templateFile: "templates/Component.tsx.hbs"
},
{
type: "add",
path: "packages/myteam-components/src/{{pascalCase name}}.test.tsx",
templateFile: "templates/Component.test.tsx.hbs"
}
]
});
// Generate team app page
plop.setGenerator("page", {
description: "Create a team app page",
prompts: [
{
type: "input",
name: "name",
message: "Page name:"
},
{
type: "list",
name: "app",
message: "Which app?",
choices: ["myteam-dashboard", "myteam-mobile"]
}
],
actions: [
{
type: "add",
path: "apps/{{app}}/src/app/{{kebabCase name}}/page.tsx",
templateFile: "templates/Page.tsx.hbs"
}
]
});
}Run:
cd teams/myteam
pnpm plop component
pnpm plop pageTeam workspace CI/CD optimization
Only run CI for affected code:
# .github/workflows/myteam-ci.yml
name: MyTeam CI
on:
pull_request:
paths:
- "teams/myteam/**"
- "packages/**" # Shared packages affect all teams
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
team-changed: ${{ steps.filter.outputs.team }}
packages-changed: ${{ steps.filter.outputs.packages }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
team:
- 'teams/myteam/**'
packages:
- 'packages/**'
build:
needs: detect-changes
if: needs.detect-changes.outputs.team-changed == 'true' || needs.detect-changes.outputs.packages-changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- run: pnpm install
# Only test team code if it changed
- if: needs.detect-changes.outputs.team-changed == 'true'
run: pnpm --filter=./teams/myteam/** test
# Always build (dependencies might have changed)
- run: pnpm --filter=./teams/myteam/** buildTeam-specific deployment strategies
Vercel monorepo deployment per team:
// teams/myteam/apps/myteam-dashboard/vercel.json
{
"buildCommand": "cd ../../../ && pnpm --filter=myteam-dashboard build",
"outputDirectory": ".next",
"installCommand": "cd ../../../ && pnpm install",
"framework": "nextjs",
"ignoreCommand": "bash -c 'git diff HEAD^ HEAD --quiet teams/myteam/ packages/'",
"env": {
"DATABASE_URL": "@myteam-database-url",
"AUTH_SECRET": "@myteam-auth-secret"
}
}Deploy only when team code changes:
#!/bin/bash
# teams/myteam/infra/scripts/should-deploy.sh
# Check if team code or shared packages changed
git diff HEAD^ HEAD --quiet teams/myteam/ packages/
if [ $? -ne 0 ]; then
echo "Changes detected, deploying..."
exit 0
else
echo "No changes, skipping deployment"
exit 1
fiResource isolation between teams
Database per team:
// teams/myteam/infra/database/schema.prisma
datasource db {
provider = "postgresql"
url = env("MYTEAM_DATABASE_URL")
}
model TeamUser {
id String @id @default(cuid())
teamId String @default("myteam")
email String @unique
role String
createdAt DateTime @default(now())
@@index([teamId])
}Separate Prisma clients per team:
// teams/myteam/packages/db/src/index.ts
import { PrismaClient } from "@prisma/client";
// Team-specific Prisma client
export const teamDb = new PrismaClient({
datasources: {
db: {
url: process.env.MYTEAM_DATABASE_URL
}
}
});
// Typed helper for team queries
export async function getTeamUser(email: string) {
return teamDb.teamUser.findUnique({
where: { email, teamId: "myteam" }
});
}Team workspace metrics and monitoring
Track team workspace health:
// packages/workspace-metrics/src/index.ts
import { readdir } from "node:fs/promises";
import { join } from "node:path";
export async function getTeamMetrics(teamId: string) {
const teamPath = join(process.cwd(), "teams", teamId);
const [apps, packages] = await Promise.all([readdir(join(teamPath, "apps")), readdir(join(teamPath, "packages"))]);
return {
teamId,
appCount: apps.length,
packageCount: packages.length,
totalSize: await getDirectorySize(teamPath)
};
}
async function getDirectorySize(path: string): Promise<number> {
// Implementation
return 0;
}Dashboard for all teams:
// Platform dashboard showing team workspace metrics
import { getTeamMetrics } from "@repo/workspace-metrics";
const metrics = await Promise.all([getTeamMetrics("ai"), getTeamMetrics("istudio"), getTeamMetrics("myteam")]);
console.table(metrics);
// ┌─────────┬──────────┬───────────────┬───────────┐
// │ teamId │ appCount │ packageCount │ totalSize │
// ├─────────┼──────────┼───────────────┼───────────┤
// │ ai │ 2 │ 3 │ 15.2 MB │
// │ istudio │ 3 │ 2 │ 22.8 MB │
// │ myteam │ 2 │ 2 │ 8.5 MB │
// └─────────┴──────────┴───────────────┴───────────┘Related documentation
- Creating Packages: Package Development →
- Workspace Architecture: Architecture Overview →
- Dependencies: Dependency Management →
- Conventions: Coding Conventions →
- Platform Apps: Platform Applications →
- Deployment: CI/CD Setup →
Creating Packages
Step-by-step guide to creating shared packages in the OneApp monorepo — from setup to publishing, with complete configuration examples.
API SDK Integration
End-to-end type-safe API integration — from Prisma schema to TypeScript SDK, with automatic OpenAPI generation and client code generation.