8Space uses npm workspaces to manage a monorepo with two main packages: the landing page and the core application.
Root Configuration
{
"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
packages/landing
Database
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
packages/landing/
├── app/ # Next.js App Router
│ ├── (marketing)/ # Marketing pages
│ ├── api/ # API routes
│ └── layout.tsx
├── components/
│ ├── ui/ # UI components
│ └── sections/ # Page sections
├── lib/ # Utilities
├── public/ # Static assets
├── styles/
├── package.json
├── next.config.mjs
└── tsconfig.json
packages/app/supabase/migrations/
├── 20260212220000_init_gantt_mvp.sql
├── 20260213220000_auto_join_new_users.sql
├── 20260213220100_handle_new_user_google_compat.sql
├── 20260216220000_fix_create_project_trigger_conflict.sql
└── 20260217143000_multi_tenant_onboarding.sql
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. App-Specific Dependencies
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"
Landing-Specific Dependencies
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
Create Migration
supabase migration new <description>
Write SQL
Add table definitions, functions, and policies
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