Skip to main content
8Space uses npm workspaces to manage a monorepo with two main packages: the landing page and the core application.

Root Configuration

packages/package.json
{
  "name": "8space-monorepo",
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "scripts": {
    "dev": "node scripts/dev-all.mjs",
    "dev:all": "node scripts/dev-all.mjs",
    "dev:landing": "npm run dev -w packages/landing",
    "dev:app": "npm run dev -w packages/app",
    "build:landing": "npm run build -w packages/landing",
    "build:app": "npm run build -w packages/app"
  }
}

Package Overview

packages/landing

Marketing site built with Next.js 15Tech Stack:
  • Next.js 15.5.9 with App Router
  • React 19
  • Tailwind CSS + DaisyUI
  • MDX for content pages
  • Supabase Auth integration

packages/app

Main SaaS application built with ViteTech Stack:
  • React 19 with TypeScript
  • Vite 7 for build tooling
  • TanStack Query for data fetching
  • Radix UI primitives
  • Supabase client

Application Structure

packages/app/
├── src/
│   ├── components/
│   │   ├── app/          # Application-specific components
│   │   ├── auth/         # Authentication components
│   │   └── ui/           # Reusable UI components
│   ├── domain/
│   │   ├── repositories/ # Repository pattern implementations
│   │   │   ├── interfaces.ts   # Repository interfaces
│   │   │   ├── supabase.ts     # Supabase implementation
│   │   │   └── index.ts
│   │   └── types.ts      # Domain types and interfaces
│   ├── hooks/            # React hooks
│   ├── integrations/
│   │   └── supabase/     # Supabase client configuration
│   ├── lib/              # Utility libraries
│   ├── utils/            # Helper functions
│   └── views/            # Page-level components
├── supabase/
│   └── migrations/       # Database migrations (SQL)
├── package.json
├── vite.config.ts
└── tsconfig.json

Domain Layer Architecture

The packages/app/src/domain/ directory contains the core business logic:

Repository Interfaces

packages/app/src/domain/repositories/interfaces.ts
export interface TenantRepository {
  listTenants(userId: string): Promise<Tenant[]>;
  createTenantWithOwner(name: string, preferredSlug?: string): Promise<Tenant>;
}

export interface ProjectRepository {
  listProjects(userId: string, tenantSlug: string): Promise<Project[]>;
  createProjectWithDefaults(tenantSlug: string, input: CreateProjectInput): Promise<Project>;
  getProjectMembers(projectId: string): Promise<ProjectMember[]>;
  listWorkflowColumns(projectId: string): Promise<WorkflowColumn[]>;
  updateProjectSettings(projectId: string, input: Pick<Project, 'name' | 'description'>): Promise<Project>;
}

export interface TaskRepository {
  listTasks(projectId: string): Promise<Task[]>;
  listDependencies(projectId: string): Promise<TaskDependency[]>;
  createTask(input: CreateTaskInput): Promise<Task>;
  updateTaskInline(input: UpdateTaskInlineInput): Promise<Task>;
  moveTask(taskId: string, toColumnId: string, newRank: number): Promise<Task>;
  reorderTasks(projectId: string, orderedTaskIds: string[]): Promise<void>;
  setTaskDependencies(projectId: string, successorTaskId: string, predecessorTaskIds: string[], type?: DependencyType): Promise<void>;
  deleteTask(taskId: string): Promise<void>;
}

export interface DashboardRepository {
  getProjectMetrics(projectId: string, rangeDays: number): Promise<ProjectMetrics>;
}

Type System

All domain types are defined in packages/app/src/domain/types.ts to ensure consistency across the application.
packages/app/src/domain/types.ts
export type ProjectRole = 'owner' | 'editor' | 'viewer';
export type TenantRole = 'owner' | 'admin' | 'member';
export type TaskPriority = 'p0' | 'p1' | 'p2';
export type WorkflowColumnKind = 'backlog' | 'todo' | 'in_progress' | 'done' | 'custom';
export type DependencyType = 'FS';

export interface Project {
  id: string;
  tenantId: string;
  name: string;
  description?: string | null;
  createdBy: string;
  createdAt: string;
  archivedAt?: string | null;
  role: ProjectRole;
}

export interface Task {
  id: string;
  projectId: string;
  title: string;
  statusColumnId: string;
  assignees: UserProfile[];
  dueDate?: string | null;
  startDate?: string | null;
  priority: TaskPriority;
  orderRank: number;
  description?: string | null;
  tags: TaskLabel[];
  checklist: TaskChecklistItem[];
  attachments: TaskAttachment[];
  estimate?: number | null;
  completedAt?: string | null;
  isMilestone: boolean;
  createdAt: string;
  updatedAt: string;
}

Component Organization

Application Components

Located in packages/app/src/components/app/:
  • Kanban board components
  • Gantt chart components
  • Task detail panels
  • Project settings
  • Team management

UI Components

Located in packages/app/src/components/ui/:
  • Button, Input, Dialog (Radix UI wrappers)
  • Form components
  • Layout components
  • Theme-aware components with next-themes

Authentication Components

Located in packages/app/src/components/auth/:
  • Login/signup forms
  • OAuth provider buttons
  • Protected route wrappers

Dependency Management

Both packages share common dependencies:
"@supabase/supabase-js": "^2.95.3"
"@radix-ui/react-slot": "^1.2.4"
"tailwind-merge": "^3.4.0"
"class-variance-authority": "^0.7.1"
"clsx": "^2.1.1"
These are installed at the workspace root and shared across packages.
packages/app/package.json
"@tanstack/react-query": "^5.90.21"
"@tanstack/react-query-devtools": "^5.91.3"
"@dnd-kit/core": "^6.3.1"
"@dnd-kit/sortable": "^10.0.0"
"date-fns": "^4.1.0"
"react-router-dom": "^7.13.0"
packages/landing/package.json
"next": "15.5.9"
"@mdx-js/loader": "^2.3.0"
"@mdx-js/react": "^2.3.0"
"framer-motion": "^12.34.0"
"stripe": "^13.11.0"
"resend": "^6.0.0"

Build Configuration

Vite Configuration (App)

The main application uses Vite for fast development and optimized production builds:
packages/app/vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
})

Next.js Configuration (Landing)

The landing page uses Next.js with MDX support:
packages/landing/next.config.mjs
import createMDX from '@next/mdx'

const withMDX = createMDX({
  extension: /\.mdx?$/,
})

export default withMDX({
  pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
})

Development Scripts

# Start all services
npm run dev

# Start individual packages
npm run dev:app
npm run dev:landing

Migration Management

Database migrations are stored in packages/app/supabase/migrations/ with timestamp-based naming:
[YYYYMMDDHHMMSS]_[description].sql
1

Create Migration

supabase migration new <description>
2

Write SQL

Add table definitions, functions, and policies
3

Apply Locally

supabase db reset
4

Deploy

supabase db push

Best Practices

Type Safety

  • Use TypeScript strict mode
  • Define types in domain/types.ts
  • Generate types from database schema

Code Organization

  • Keep domain logic in domain/
  • Separate UI from business logic
  • Use repository pattern for data access

State Management

  • Use TanStack Query for server state
  • React Context for UI state
  • URL state for navigation

Testing

  • Mock repositories for unit tests
  • Integration tests with test database
  • E2E tests for critical paths

Next Steps

Database Schema

Explore the PostgreSQL schema and table relationships

Build docs developers (and LLMs) love