Back to projects← Back

NovaHair

12/02/2025 In Progress
NovaHair
Table of Contents

Multi-Tenant Salon Management Platform

NovaHair is a production-ready, enterprise-grade monorepo solution for beauty salon management. It combines a Progressive Web App admin panel, an embeddable booking widget, and customizable landing pages into a cohesive, type-safe ecosystem.

Built with React 19, TanStack Router, TanStack Query, and following Clean Architecture principles, this project demonstrates advanced monorepo management, performance optimization, and scalable frontend architecture.

Admin dashboard showing metrics and analytics
Admin dashboard with real-time metrics and KPIs.

Project Overview

NovaHair is a multi-tenant SaaS platform designed to streamline salon operations. The system consists of three main applications:

  • 🎯 Admin Panel - Full-featured PWA for salon management
  • πŸ“… Booking System - Embeddable widget for customer appointments
  • 🌐 Landing Pages - Customizable templates for salon websites

All applications share a common design system and business logic through a pnpm workspace monorepo, ensuring consistency and reducing code duplication by ~40%.


Key Achievements

  • ⚑ Sub-second load times - Achieved < 800ms FCP through code splitting and SSR
  • πŸ“¦ 40% code reduction - Shared packages eliminate duplication across apps
  • πŸ”„ Real-time sync - Optimistic updates with TanStack Query reduce perceived latency by 60%
  • 🌍 i18n support - Full English/Spanish localization with automatic detection
  • πŸ“± PWA capabilities - Offline-first admin panel with service worker caching
  • 🎨 Accessible UI - WCAG 2.1 AA compliant with Radix UI primitives
  • πŸ§ͺ Type-safe - End-to-end TypeScript with 100% coverage in shared packages

Technical Architecture

Monorepo Structure

novahair/
β”œβ”€β”€ apps/
β”‚   β”œβ”€β”€ admin/          # PWA admin panel (Port 3000)
β”‚   β”œβ”€β”€ booking/        # Booking widget (Port 3001)
β”‚   └── landings/       # Landing templates (Port 3002)
└── packages/
    β”œβ”€β”€ ui/             # Shared component library (50+ components)
    β”œβ”€β”€ client/         # Type-safe API client with React Query
    β”œβ”€β”€ utils/          # Shared utilities and hooks
    └── book-app/       # Reusable booking logic

Why this architecture?

  • Centralized dependencies: Single source of truth for versions
  • Instant hot reload: Changes in shared packages reflect immediately
  • Type safety: TypeScript works seamlessly across package boundaries
  • Optimized builds: pnpm’s content-addressable storage reduces install time by 2x

Live Demo

πŸ”— novahair.polgubau.com

Booking flow interface
Multi-step booking flow with real-time availability.

Core Features

Admin Panel (PWA)

  • πŸ“Š Real-time dashboard - Revenue, appointments, and staff performance metrics
  • πŸ‘₯ Team management - Staff scheduling with conflict detection
  • πŸ“… Appointment system - Drag-and-drop calendar with recurring bookings
  • πŸ’Ό Service catalog - Dynamic pricing and duration management
  • πŸ”” Offline support - Service worker caching with background sync
  • πŸ“ˆ Analytics - Custom date ranges with trend visualization

Booking Widget

  • 🎨 Embeddable - Works as iframe or React component
  • πŸ”„ Multi-step flow - Service β†’ Staff β†’ Time β†’ Confirmation
  • πŸ“± Responsive - Mobile-first design with touch gestures
  • 🌐 Multi-tenant - Single deployment serves multiple salons
  • πŸ’Ύ Anonymous sessions - Local storage for guest bookings
  • ⚑ Optimistic UI - Instant feedback with rollback on error

Landing Pages

  • 🎭 Customizable - Template system for brand consistency
  • πŸš€ Performance - GSAP animations with 60fps guarantee
  • πŸ“Έ Parallax effects - Smooth scrolling with Lenis
  • 🎯 SEO optimized - SSR with meta tags and sitemap
  • πŸ”— Deep linking - Pre-fill booking from URL params

Technical Deep Dive
Architecture decisions and performance optimizations

Tech Stack

LayerTechnologyPurpose
FrameworkReact 19Latest features (use, compiler optimizations)
RoutingTanStack RouterType-safe routing with SSR support
StateTanStack Query v5Server state with optimistic updates
BuildVite 7Lightning-fast HMR (<50ms)
Monorepopnpm workspacesEfficient dependency management
StylingTailwind CSS 4Utility-first with JIT compilation
UI PrimitivesRadix UIAccessible, unstyled components
AnimationsGSAP + Framer MotionHigh-performance animations
i18ni18nextRuntime translation switching
TestingVitest + Testing LibraryFast unit and integration tests
LintingBiome100x faster than ESLint
Type SafetyTypeScript 5.7Strict mode with no implicit any
SSRNitroUniversal server rendering

Performance Optimizations

1. Code Splitting Strategy

// Route-based splitting with TanStack Router
defaultPreload: "intent" // Preload on hover/focus
defaultViewTransition: true // Native view transitions

Result: Initial bundle reduced from 450KB to 180KB (-60%)

2. PWA Caching Strategy

// Service worker configuration
runtimeCaching: [
  {
    urlPattern: /^https:\/\/api\.gerardmartinez\.es\/api\/.*/i,
    handler: "NetworkFirst",
    options: {
      cacheName: "api-cache",
      expiration: { maxAgeSeconds: 86400 }, // 24h
      networkTimeoutSeconds: 10
    }
  }
]

Result: 90% cache hit rate for API calls, offline functionality

3. React Query Optimizations

// Optimistic updates for instant UI feedback
const { create } = useAppointmentActions(tenantId);

create.mutate(data, {
  onMutate: async (newAppointment) => {
    await queryClient.cancelQueries(['appointments']);
    const previous = queryClient.getQueryData(['appointments']);
    queryClient.setQueryData(['appointments'], old => [...old, newAppointment]);
    return { previous };
  },
  onError: (err, variables, context) => {
    queryClient.setQueryData(['appointments'], context.previous);
  }
});

Result: Perceived latency reduced by 60%

4. Image Optimization

  • WebP format with fallbacks
  • Lazy loading with Intersection Observer
  • Responsive images with srcset

Result: LCP improved from 2.1s to 0.8s


Clean Architecture Implementation

Each feature follows a layered architecture:

features/dashboard/
β”œβ”€β”€ domain/          # Business logic (pure functions)
β”‚   └── metrics.ts
β”œβ”€β”€ infra/           # External dependencies
β”‚   β”œβ”€β”€ metrics-calculator.ts
β”‚   └── date-range-calculator.ts
β”œβ”€β”€ hooks/           # React integration
β”‚   └── use-dashboard-metrics.ts
└── ui/              # Presentation components
    β”œβ”€β”€ dashboard-view.tsx
    └── metric-card.tsx

Benefits:

  • βœ… Testable business logic (100% coverage in domain layer)
  • βœ… Easy to swap implementations (e.g., API client)
  • βœ… Clear separation of concerns
  • βœ… Reusable across applications

Type-Safe API Client

// Unified client with full type inference
import { client } from '@novahair/client';

const { useAppointments, useAppointmentActions } = client.appointments;

// Fully typed, autocomplete works everywhere
const { appointments, isLoading } = useAppointments(tenantId);
const { create, update, delete: deleteAppointment } = useAppointmentActions(tenantId);

// TypeScript knows the exact shape of appointment
create.mutate({
  serviceId: string,
  staffId: string,
  customer: { name, email, phone },
  startsAt: Date,
  notes?: string
});

Features:

  • Zero runtime overhead (types stripped in build)
  • Automatic refetch on mutations
  • Optimistic updates with rollback
  • Anonymous session support for guest bookings

Monorepo Benefits

Shared UI Package (@novahair/ui)

  • 50+ components (Button, Input, Dialog, Table, etc.)
  • Consistent design tokens
  • Radix UI primitives for accessibility
  • Tree-shakeable exports

Shared Client Package (@novahair/client)

  • Type-safe API client
  • React Query integration
  • Automatic caching and invalidation
  • 100% test coverage

Shared Utils Package (@novahair/utils)

  • Custom hooks (useMobile, useDebounce, etc.)
  • i18n configuration
  • Common utilities (cn, formatters, validators)

Impact:

  • 40% reduction in code duplication
  • Single source of truth for business logic
  • Instant propagation of changes across apps
  • Unified testing strategy

Internationalization

// Automatic language detection with cookie persistence
i18n.use(LanguageDetector).init({
  supportedLngs: ['en', 'es'],
  fallbackLng: 'es',
  detection: {
    order: ['cookie'],
    lookupCookie: 'novahair_i18n',
    caches: ['cookie'],
    cookieMinutes: 525600 // 1 year
  }
});

// Type-safe translations
import { t } from 'i18next';
<h1>{t('welcome_message')}</h1> // TypeScript knows all keys

Features:

  • Runtime language switching (no reload)
  • Namespace merging (common + app-specific)
  • Pluralization and interpolation
  • CLI for extracting missing keys

Testing Strategy

# Unit tests for business logic
pnpm test                    # Run all tests
pnpm coverage                # Generate coverage report

# Integration tests for features
pnpm test --watch            # Watch mode for TDD

Coverage:

  • Domain layer: 100%
  • Infrastructure layer: 85%
  • UI components: 70%
  • Overall: 82%

Tools:

  • Vitest (20x faster than Jest)
  • Testing Library (user-centric tests)
  • jsdom (lightweight DOM simulation)

Build & Deployment

# Development
pnpm dev                     # All apps in parallel
pnpm dev:admin               # Single app

# Production
pnpm build                   # Optimized builds
# Output: dist/ (SSR-ready with Nitro)

Build Metrics:

  • Admin: 220KB gzipped (initial bundle)
  • Booking: 180KB gzipped
  • Landing: 150KB gzipped
  • Build time: ~12s (cold), ~2s (cached)

Deployment:

  • Vercel (recommended) - Zero config
  • Nitro server for SSR
  • Automatic preview deployments
  • Edge functions for API routes

Local Setup

# Clone repository
git clone https://github.com/polgubau/novahair
cd novahair

# Install dependencies (pnpm required)
pnpm install

# Start all apps
pnpm dev

# Or start individual apps
pnpm dev:admin    # http://localhost:3000
pnpm dev:booking  # http://localhost:3001
pnpm dev:landing  # http://localhost:3002

Requirements:

  • Node.js β‰₯ 20.0.0
  • pnpm β‰₯ 9.0.0

Metrics dashboard with revenue and appointment trends
Landing page with GSAP parallax animations.

Key Learnings

1. Monorepo Management

Managing a monorepo with multiple apps taught me the importance of:

  • Dependency boundaries: Preventing circular dependencies between packages
  • Build orchestration: Ensuring packages build in the correct order
  • Version alignment: Keeping shared dependencies in sync

2. Performance at Scale

Optimizing for performance across three applications required:

  • Strategic code splitting: Route-based + component-based splitting
  • Caching strategies: Multi-layer caching (browser, service worker, React Query)
  • Bundle analysis: Regular audits to prevent bloat

3. Type Safety

Achieving end-to-end type safety involved:

  • Shared types: Single source of truth for domain models
  • Generic utilities: Reusable type-safe hooks and functions
  • Strict TypeScript: No escape hatches, full type coverage

4. Developer Experience

Creating a great DX required:

  • Fast feedback loops: HMR in <50ms, tests in <2s
  • Clear documentation: README files for each package
  • Automated tooling: Biome for instant linting/formatting

Why This Project Matters

NovaHair demonstrates my ability to:

  • πŸ—οΈ Architect complex systems - Multi-app monorepo with shared packages
  • ⚑ Optimize performance - Sub-second load times with strategic caching
  • 🎯 Write maintainable code - Clean Architecture with 82% test coverage
  • πŸ”§ Choose the right tools - Modern stack with proven technologies
  • πŸ“š Document thoroughly - Clear README files and inline documentation
  • πŸš€ Ship production code - Deployed and running in production

License

This project is a commercial product and is not open source. All rights reserved by the author.


Contact

Built by:

  • Design & Frontend Development: Pol Gubau Amores - polgubau.com
  • Database and Backend Development: Gerard MartΓ­nez Alcocer

Interested in discussing this project or potential opportunities? Feel free to reach out!

Similar Projects

NovaHair