DEV Community

Khanh Tran
Khanh Tran

Posted on

Building a Scalable Frontend Monorepo with Turborepo, Vite, TailwindCSS V4, React 19, Tanstack Router, Tanstack Form

Building a Scalable Frontend Monorepo: Architecture Best Practices

Introduction

In modern frontend development, managing multiple applications and shared packages efficiently is crucial for maintaining code quality, enforcing consistency, and accelerating development velocity. This article introduces a production-ready monorepo architecture that demonstrates best practices for structuring large-scale frontend projects.

This Monorepo UI Starter is built with cutting-edge tools and follows industry-standard patterns to solve common challenges in enterprise frontend development:

  • πŸš€ Code Sharing: Reusable components, utilities, and configurations across multiple apps
  • πŸ—οΈ Scalability: Clear separation of concerns with a modular package structure
  • ⚑ Performance: Optimized builds with caching and parallel execution via Turborepo
  • 🎨 Developer Experience: Integrated Storybook for component development and documentation
  • πŸ“¦ Type Safety: End-to-end TypeScript with shared type definitions
  • πŸ”§ Maintainability: Consistent linting, formatting, and build configurations

Key Features

Modern Stack

  • React 19 with React Compiler for automatic optimization
  • Vite 7 for lightning-fast builds and HMR
  • TypeScript 5.9 with strict type checking
  • Tailwind CSS v4 for utility-first styling

Complete Form System

  • TanStack Form integration with type-safe form state management
  • Pre-built form components (TextField, SelectField, SubmitButton)
  • Lazy loading for optimal bundle size
  • Automatic validation and error handling

Data Management

  • TanStack Query for server state management
  • Separate packages for queries and mutations
  • Automatic caching and background refetching
  • Optimistic updates support

Type-Safe Routing

  • TanStack Router with file-based routing
  • Auto-generated route tree
  • Protected route groups
  • Type-safe navigation and params

Component Library

  • Mantine v8 + Tailwind v4 hybrid approach
  • Atomic design structure (atoms, molecules, templates)
  • Storybook for component documentation
  • Phosphor Icons for consistent iconography

Developer Tools

  • Turborepo for build orchestration
  • pnpm for fast, efficient package management
  • ESLint and Prettier for code quality
  • Type checking across all packages

Technology Stack

Core Tools

  • Turborepo: High-performance build system for JavaScript/TypeScript monorepos (v2.6.1)
  • pnpm: Fast, disk space efficient package manager with excellent monorepo support (v9.0.0)
  • React 19: Latest version with React Compiler support (v19.2.0)
  • Vite 7: Next-generation frontend build tool (v7.2.4)
  • TypeScript 5.9: Strict type checking across the entire codebase (v5.9.3)

UI & Styling

  • Mantine: Comprehensive React component library (v8.3.8)
  • Tailwind CSS v4: Utility-first CSS framework (v4.1.17)
  • Storybook: Component development and documentation environment (v10.0.8)
  • Phosphor Icons: Flexible icon family (v2.1.10)

Application Framework

  • TanStack Router: Type-safe routing with automatic route generation (v1.139.1)
  • TanStack Query: Powerful asynchronous state management (v5.90.10)
  • TanStack Form: Headless, type-safe form state management (v1.26.0)
  • Axios: HTTP client for API requests (v1.13.2)
  • Zod: TypeScript-first schema validation (v4.1.12)

Utility Libraries

Monorepo Architecture

High-Level Structure

monorepo-ui-starter/ β”œβ”€β”€ apps/ # Deployable applications β”‚ β”œβ”€β”€ web/ # Main web application β”‚ └── storybook/ # Component documentation β”œβ”€β”€ packages/ # Shared internal packages β”‚ β”œβ”€β”€ ui/ # Component library β”‚ β”œβ”€β”€ api-client/ # API layer β”‚ β”œβ”€β”€ hooks/ # React hooks & queries β”‚ β”œβ”€β”€ types/ # TypeScript definitions β”‚ β”œβ”€β”€ utils/ # Utility functions β”‚ β”œβ”€β”€ eslint-config/ # Shared ESLint configs β”‚ └── typescript-config/ # Shared TypeScript configs β”œβ”€β”€ package.json # Root workspace configuration β”œβ”€β”€ pnpm-workspace.yaml # pnpm workspace definition └── turbo.json # Turborepo pipeline configuration 
Enter fullscreen mode Exit fullscreen mode

Design Philosophy

This architecture follows the "apps and packages" pattern, which separates:

  1. Applications (apps/): Deployable, user-facing applications
  2. Packages (packages/): Reusable internal libraries and configurations

This separation ensures clear boundaries and enables:

  • Independent deployment of applications
  • Shared code without duplication
  • Incremental adoption of shared packages
  • Clear dependency graphs

Deep Dive: Package Structure

1. apps/web - Main Application

The primary React application built with Vite and modern routing patterns.

Key Features:

  • File-based routing with TanStack Router
  • Route generation via @tanstack/router-plugin
  • Authentication context with protected routes
  • Layout system for consistent UI structure
  • Feature-based organization for scalability

Directory Structure:

web/ β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ main.tsx # App entry point β”‚ β”œβ”€β”€ routeTree.gen.ts # Auto-generated routes β”‚ β”œβ”€β”€ components/ β”‚ β”‚ └── layouts/ # Layout components β”‚ β”‚ └── MainLayout/ β”‚ β”œβ”€β”€ context/ β”‚ β”‚ └── auth.tsx # Auth state management β”‚ β”œβ”€β”€ features/ # Feature modules β”‚ β”‚ β”œβ”€β”€ Home/ β”‚ β”‚ β”œβ”€β”€ Login/ β”‚ β”‚ β”œβ”€β”€ Users/ β”‚ β”‚ β”œβ”€β”€ AddUser/ β”‚ β”‚ └── UserDetail/ β”‚ β”œβ”€β”€ hooks/ # App-specific hooks β”‚ β”œβ”€β”€ routes/ # Route definitions β”‚ β”‚ β”œβ”€β”€ __root.tsx β”‚ β”‚ β”œβ”€β”€ (auth)/ # Auth route group β”‚ β”‚ β”‚ └── login.tsx β”‚ β”‚ └── (main)/ # Protected route group β”‚ β”‚ β”œβ”€β”€ index.tsx β”‚ β”‚ β”œβ”€β”€ route.tsx β”‚ β”‚ └── users/ β”‚ β”‚ β”œβ”€β”€ index.tsx # /users β”‚ β”‚ β”œβ”€β”€ create.tsx # /users/create β”‚ β”‚ └── $userId.tsx # /users/:userId β”‚ └── styles/ β”‚ └── globals.css β”œβ”€β”€ package.json └── vite.config.ts 
Enter fullscreen mode Exit fullscreen mode

Best Practices Demonstrated:

  1. Feature-Based Organization: Each feature (Home, Users, Login) is self-contained with its own components and logic
  2. Route Grouping: Using (auth) and (main) for logical separation without affecting URLs
  3. Type-Safe Routing: TanStack Router provides full TypeScript support
  4. Context Separation: Auth logic separated from components

2. apps/storybook - Component Documentation

Dedicated Storybook instance for developing and documenting the UI component library.

Purpose:

  • Visual component testing
  • Interactive documentation
  • Design system showcase
  • Isolated development environment

Key Files:

storybook/ β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ examples/ # Example implementations β”‚ β”‚ β”œβ”€β”€ Icons.tsx β”‚ β”‚ └── table.tsx β”‚ └── stories/ # Component stories β”‚ β”œβ”€β”€ Button.stories.ts β”‚ β”œβ”€β”€ Input.stories.ts β”‚ β”œβ”€β”€ TextInput.stories.ts β”‚ β”œβ”€β”€ Table.stories.ts β”‚ β”œβ”€β”€ EmptyState.stories.tsx β”‚ └── Icons.stories.ts └── package.json 
Enter fullscreen mode Exit fullscreen mode

3. packages/ui - Component Library

The heart of the design system, providing reusable React components.

Architecture Highlights:

  • Atomic Design Pattern: Organized into atoms, molecules, organisms, and templates
  • Polymorphic Components: Using Mantine's createPolymorphicComponent for flexibility
  • Tailwind + Mantine Hybrid: Leverages both utility classes and component library
  • CSS Export: Exports globals.css for consistent styling

Structure:

ui/ β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ globals.css # Global styles β”‚ β”œβ”€β”€ assets/ β”‚ β”‚ └── images/ β”‚ β”œβ”€β”€ atoms/ # Basic building blocks β”‚ β”‚ β”œβ”€β”€ ActionIcon.tsx β”‚ β”‚ β”œβ”€β”€ Button.tsx β”‚ β”‚ β”œβ”€β”€ Input.tsx β”‚ β”‚ β”œβ”€β”€ TextInput.tsx β”‚ β”‚ β”œβ”€β”€ Select.tsx β”‚ β”‚ β”œβ”€β”€ Table.tsx β”‚ β”‚ β”œβ”€β”€ Icon.tsx β”‚ β”‚ β”œβ”€β”€ EmptyState.tsx β”‚ β”‚ β”œβ”€β”€ CopyButton.tsx β”‚ β”‚ β”œβ”€β”€ UIProvider.tsx β”‚ β”‚ └── index.ts β”‚ β”œβ”€β”€ molecules/ # Composite components β”‚ β”‚ β”œβ”€β”€ Form/ # TanStack Form integration β”‚ β”‚ β”‚ β”œβ”€β”€ components/ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ FormContainer.tsx β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ FormTextField.tsx β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ FormSelectField.tsx β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ SubmitButton.tsx β”‚ β”‚ β”‚ β”‚ └── index.ts β”‚ β”‚ β”‚ β”œβ”€β”€ context.tsx β”‚ β”‚ β”‚ └── index.ts β”‚ β”‚ └── index.ts β”‚ └── templates/ # Page templates └── package.json 
Enter fullscreen mode Exit fullscreen mode

Component Example:

// Wrapping Mantine with custom styling import { Button as MButton, type ButtonProps as MButtonProps } from '@mantine/core'; const Button = createPolymorphicComponent<'button', ButtonProps>( forwardRef<HTMLButtonElement, ButtonProps>(({ ...props }, ref) => ( <MButton {...props} ref={ref} classNames={{ root: 'focus:ring-2 focus:ring-brand-400', section: '[&_svg]:text-brand-300', }} />  )) ); 
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Consistent component API across the application
  • Easy customization through Tailwind classes
  • Type-safe props with TypeScript
  • Accessible components out of the box (Mantine)

Form System with TanStack Form:

The UI package includes a sophisticated form system built on TanStack Form v1.26.0, providing type-safe, headless form state management:

// Form hook creation with field and form components import {lazy} from "react"; import {createFormHook} from "@tanstack/react-form"; const TextField = lazy(() => import('./FormTextField')); const SelectField = lazy(() => import('./FormSelectField')); const SubmitButton = lazy(() => import('./SubmitButton')) export const {useAppForm, withForm, withFieldGroup} = createFormHook({ fieldComponents: { TextField, SelectField }, formComponents: { SubmitButton, }, fieldContext, formContext, }); 
Enter fullscreen mode Exit fullscreen mode

Form Components:

  • FormContainer: Wrapper component for form layout
  • FormTextField: Text input field with validation
  • FormSelectField: Select dropdown field
  • SubmitButton: Form submission button with loading state

This approach provides:

  • Lazy loading for optimal bundle size
  • Type-safe field definitions
  • Automatic validation and error handling
  • Consistent form behavior across the application

4. packages/api-client - API Layer

Centralized HTTP client with type-safe API endpoints.

Structure:

api-client/ β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ index.ts # Main client export β”‚ β”œβ”€β”€ axios.ts # Axios configuration β”‚ β”œβ”€β”€ user.ts # User endpoints β”‚ └── product.ts # Product endpoints └── package.json 
Enter fullscreen mode Exit fullscreen mode

Key Concepts:

  1. Client Configuration: Single source for base URL, auth tokens, and interceptors
  2. Resource Organization: Each domain (user, product) has its own file
  3. Type Integration: Uses shared types from @repo/types
  4. Token Management: Integrates with utility functions for auth

Benefits:

  • Centralized API logic
  • Easy to mock for testing
  • Consistent error handling
  • Reusable across multiple apps

5. packages/hooks - React Hooks & Queries

Custom hooks and TanStack Query integration layer.

Structure:

hooks/ β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ lib/ # Utility hooks β”‚ β”‚ β”œβ”€β”€ api.ts β”‚ β”‚ └── index.ts β”‚ β”œβ”€β”€ queries/ # TanStack Query hooks β”‚ β”‚ β”œβ”€β”€ index.ts β”‚ β”‚ β”œβ”€β”€ user.ts β”‚ β”‚ └── product.ts β”‚ └── mutations/ # TanStack Query mutations β”‚ β”œβ”€β”€ index.ts β”‚ └── user.ts └── package.json 
Enter fullscreen mode Exit fullscreen mode

Multi-path Export Strategy:

{ "exports": { "./lib": "./src/lib/index.ts", "./queries": "./src/queries/index.ts", "./mutations": "./src/mutations/index.ts" } } 
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Separation of general hooks from data-fetching hooks
  • Server state management with TanStack Query
  • Consistent data fetching patterns (queries) and mutation patterns
  • Automatic caching and refetching
  • Optimistic updates and error handling for mutations

6. packages/types - Type Definitions

Shared TypeScript interfaces and types.

Structure:

types/ β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ index.ts # Main exports β”‚ β”œβ”€β”€ user.ts # User types β”‚ β”œβ”€β”€ product.ts # Product types β”‚ └── response.ts # API response types └── package.json 
Enter fullscreen mode Exit fullscreen mode

Best Practices:

  • Domain-based type organization
  • Export both types and validators
  • Single source of truth for data structures

7. packages/utils - Utility Functions

Pure utility functions for common operations.

Structure:

utils/ β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ index.ts β”‚ └── cookies.ts # Cookie management └── package.json 
Enter fullscreen mode Exit fullscreen mode

Purpose:

  • Framework-agnostic utilities
  • Shared business logic
  • Helper functions
  • No external dependencies (when possible)

8. Configuration Packages

packages/eslint-config

Shared ESLint configurations for consistent code quality.

eslint-config/ β”œβ”€β”€ eslint.base.js # Base config β”œβ”€β”€ eslint.react.js # React-specific rules └── package.json 
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Consistent linting across all packages
  • Easy to update rules globally
  • Different configs for different package types

packages/typescript-config

Shared TypeScript configurations.

typescript-config/ β”œβ”€β”€ tsconfig.base.json # Base config β”œβ”€β”€ tsconfig.vite.json # Vite-specific config └── package.json 
Enter fullscreen mode Exit fullscreen mode

Configuration Inheritance:

// In app's tsconfig.json { "extends": "@repo/typescript-config/tsconfig.vite.json", "compilerOptions": { // App-specific overrides } } 
Enter fullscreen mode Exit fullscreen mode

Workspace Configuration

pnpm Workspace

pnpm-workspace.yaml:

packages: - "apps/*" - "packages/*" 
Enter fullscreen mode Exit fullscreen mode

This simple configuration tells pnpm to treat all directories under apps/ and packages/ as workspace packages.

Benefits:

  • Single node_modules at root (with proper hoisting)
  • Fast installation with content-addressable storage
  • Strict dependency management
  • Workspace protocol for internal packages

Turborepo Pipeline

turbo.json:

{ "$schema": "https://turborepo.com/schema.json", "ui": "tui", "tasks": { "build": { "dependsOn": ["^build"], "inputs": ["$TURBO_DEFAULT$", ".env*"], "outputs": [".next/**", "!.next/cache/**"] }, "lint": { "dependsOn": ["^lint"] }, "check-types": { "dependsOn": ["^check-types"] }, "dev": { "cache": false, "persistent": true } } } 
Enter fullscreen mode Exit fullscreen mode

Key Concepts:

  1. Task Dependencies: ^build means "run build in dependencies first"
  2. Caching: Automatic caching of build outputs for fast rebuilds
  3. Parallel Execution: Independent tasks run in parallel
  4. Incremental Builds: Only rebuild changed packages

Workflow Example:

# Build everything (with dependency order) pnpm build # Build only web app and its dependencies pnpm build --filter=web # Develop specific app pnpm dev --filter=storybook 
Enter fullscreen mode Exit fullscreen mode

Dependency Management Strategy

Workspace Dependencies

Internal packages reference each other using the workspace:* protocol:

{ "dependencies": { "@repo/ui": "workspace:*", "@repo/hooks": "workspace:*", "@repo/types": "workspace:*" } } 
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Always uses local version during development
  • Prevents version mismatches
  • Clear indication of internal dependencies

Peer Dependencies

UI components declare React as a peer dependency:

{ "peerDependencies": { "react": "^19.2.0", "react-dom": "^19.2.0" } } 
Enter fullscreen mode Exit fullscreen mode

Why:

  • Ensures single React instance across the app
  • Prevents duplicate React in bundle
  • Consumer controls the React version

Version Synchronization

Common dependencies use the same version across packages:

  • typescript: ~5.9.3 (all packages)
  • eslint: ^9.39.1 (all packages)
  • react and react-dom: ^19.2.0 (web + storybook)
  • @tanstack/react-query: ^5.90.10 (web + hooks)
  • @tanstack/react-form: ^1.26.0 (web + ui)
  • vite: ^7.2.4 (web + storybook + ui)
  • tailwindcss: ^4.1.17 (web + ui)
  • @mantine/core: ^8.3.8 (ui)

Routing Architecture

TanStack Router Features

The web app uses TanStack Router with advanced features:

1. File-Based Routing

routes/ β”œβ”€β”€ __root.tsx # Root layout β”œβ”€β”€ (auth)/ β”‚ └── login.tsx # /login └── (main)/ β”œβ”€β”€ route.tsx # Layout for protected routes β”œβ”€β”€ index.tsx # / └── users/ β”œβ”€β”€ index.tsx # /users └── $userId.tsx # /users/:userId 
Enter fullscreen mode Exit fullscreen mode

2. Route Context

export interface AppRouterContext { queryClient: QueryClient, auth: AuthContext } const router = createRouter({ routeTree, context: { queryClient, auth } }); 
Enter fullscreen mode Exit fullscreen mode

3. Type-Safe Navigation

// Auto-generated types for all routes import { Link } from '@tanstack/react-router' <Link to="/users/$userId" params={{ userId: '123' }} /> 
Enter fullscreen mode Exit fullscreen mode

State Management Patterns

1. Server State (TanStack Query)

For data from APIs:

// In @repo/hooks/queries/user.ts export const useUsers = () => { return useQuery({ queryKey: ['users'], queryFn: () => apiClient.getUsers() }); }; 
Enter fullscreen mode Exit fullscreen mode

For data mutations:

// In @repo/hooks/mutations/user.ts export const useCreateUser = () => { return useMutation({ mutationFn: (data: CreateUserData) => apiClient.users.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); } }); }; 
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Automatic caching
  • Background refetching
  • Optimistic updates
  • Loading/error states
  • Automatic query invalidation after mutations

2. Authentication State (Context)

For global auth state:

// In apps/web/src/context/auth.tsx export const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); // Auth logic return <AuthContext.Provider value={{ user }} />; }; 
Enter fullscreen mode Exit fullscreen mode

3. Local State (React Hooks)

For component-specific state:

const [isOpen, setIsOpen] = useState(false); 
Enter fullscreen mode Exit fullscreen mode

Development Workflow

Getting Started

# Install dependencies pnpm install # Develop all apps pnpm dev # Develop specific app pnpm dev --filter=web pnpm dev --filter=storybook # Build all packages pnpm build # Lint everything pnpm lint # Type check all packages pnpm check-types # Format code pnpm format 
Enter fullscreen mode Exit fullscreen mode

Adding a New Package

  1. Create package directory:
mkdir packages/new-package 
Enter fullscreen mode Exit fullscreen mode
  1. Add package.json:
{ "name": "@repo/new-package", "private": true, "version": "0.0.0", "type": "module", "exports": { ".": "./src/index.ts" } } 
Enter fullscreen mode Exit fullscreen mode
  1. The package is automatically detected by pnpm workspace

Adding a New App

Similar to package, but in apps/ directory:

mkdir apps/new-app # Add package.json with appropriate dependencies 
Enter fullscreen mode Exit fullscreen mode

Best Practices & Patterns

1. Package Naming Convention

  • Use @repo/ scope for all internal packages
  • Descriptive names: @repo/ui, @repo/api-client, @repo/hooks
  • Consistent with standard naming (e.g., eslint-config, typescript-config)

2. Export Strategy

Named Exports for Atoms:

// @repo/ui/atoms/index.ts export { default as Button } from './Button'; export { default as Input } from './Input'; export { default as TextInput } from './TextInput'; export { default as Select } from './Select'; export { default as Table } from './Table'; export { default as EmptyState } from './EmptyState'; export { default as Icon } from './Icon'; export * from './Icon'; 
Enter fullscreen mode Exit fullscreen mode

Sub-path Exports for Categories:

{ "exports": { "./globals.css": "./src/globals.css", "./atoms": "./src/atoms/index.ts", "./molecules": "./src/molecules/index.ts" } } 
Enter fullscreen mode Exit fullscreen mode

Usage in Applications:

// Import atoms import { Button, Input, Select } from '@repo/ui/atoms'; // Import molecules (Form components) import { FormContainer, useAppForm } from '@repo/ui/molecules'; // Import global styles import '@repo/ui/globals.css'; 
Enter fullscreen mode Exit fullscreen mode

3. Type Safety

  • Strict TypeScript in all packages
  • Shared types in @repo/types
  • Runtime validation with Zod where needed
  • Proper peer dependencies for React components

4. Code Organization

Feature-Based Structure in Apps:

features/ β”œβ”€β”€ Users/ β”‚ β”œβ”€β”€ Users.tsx β”‚ β”œβ”€β”€ UserList.tsx β”‚ β”œβ”€β”€ UserCard.tsx β”‚ └── index.ts 
Enter fullscreen mode Exit fullscreen mode

Atomic Design in UI Package:

ui/ β”œβ”€β”€ atoms/ # Button, Input β”œβ”€β”€ molecules/ # SearchBar, Card β”œβ”€β”€ organisms/ # Header, Sidebar └── templates/ # PageLayout 
Enter fullscreen mode Exit fullscreen mode

5. Documentation

  • README in each package explaining its purpose
  • Storybook for visual components
  • JSDoc comments for complex functions
  • TypeScript types as inline documentation

6. Performance Optimization

  • Tree-shaking: ES modules for optimal bundling
  • Code splitting: Dynamic imports for routes
  • Caching: Turborepo caches all build outputs
  • React Compiler: Enabled for automatic optimization

7. Testing Strategy

While not shown in current structure, recommended additions:

packages/ui/ β”œβ”€β”€ src/ β”‚ └── atoms/ β”‚ β”œβ”€β”€ Button.tsx β”‚ └── Button.test.tsx └── vitest.config.ts 
Enter fullscreen mode Exit fullscreen mode

Scaling Considerations

When to Create New Packages

Create a new package when:

  • βœ… Code is reused across 2+ apps
  • βœ… It has a clear, single responsibility
  • βœ… It can be developed/tested independently
  • βœ… It has stable interfaces

Don't create a package if:

  • ❌ It's only used in one app
  • ❌ It's tightly coupled to app logic
  • ❌ The API changes frequently

Adding More Apps

This structure easily supports:

  • Admin dashboard (apps/admin)
  • Mobile web app (apps/mobile)
  • Landing page (apps/landing)
  • Internal tools (apps/tools)

All sharing the same packages for consistency.

Multi-Framework Support

While this starter uses React, you could add:

  • Vue app using @repo/types and @repo/api-client
  • Node.js backend using @repo/types
  • React Native app using @repo/hooks and @repo/utils

Migration Path for Existing Projects

Step 1: Monorepo Setup

  1. Add pnpm-workspace.yaml
  2. Add turbo.json
  3. Update root package.json

Step 2: Extract Shared Code

  1. Move shared components to packages/ui
  2. Move API calls to packages/api-client
  3. Move types to packages/types

Step 3: Configure Dependencies

  1. Update imports to use @repo/*
  2. Add workspace dependencies
  3. Test builds and dev mode

Step 4: Optimize

  1. Configure Turborepo caching
  2. Set up parallel execution
  3. Add Storybook for components

Common Pitfalls & Solutions

Issue: Circular Dependencies

Solution: Use dependency injection and proper layering

utils β†’ types β†’ api-client β†’ hooks β†’ ui β†’ apps 
Enter fullscreen mode Exit fullscreen mode

Issue: Slow Installations

Solution: Use pnpm and Turborepo caching

# pnpm is much faster than npm/yarn pnpm install --frozen-lockfile 
Enter fullscreen mode Exit fullscreen mode

Issue: Type Errors Across Packages

Solution: Use project references in TypeScript

{ "references": [ { "path": "../../packages/types" } ] } 
Enter fullscreen mode Exit fullscreen mode

Issue: Hot Reload Not Working

Solution: Configure Vite to watch workspace packages

// vite.config.ts export default { server: { watch: { ignored: ['!**/node_modules/@repo/**'] } } } 
Enter fullscreen mode Exit fullscreen mode

Real-World Benefits

Development Speed

  • βœ… Shared components: Build once, use everywhere
  • βœ… Type safety: Catch errors at compile time
  • βœ… Fast builds: Turborepo cache eliminates redundant work

Code Quality

  • βœ… Consistent patterns: Shared configs enforce standards
  • βœ… Reusability: DRY principle across all apps

Team Collaboration

  • βœ… Clear ownership: Each package has clear boundaries
  • βœ… Independent work: Teams can work on different apps/packages
  • βœ… Shared knowledge: Common patterns across codebase

Maintainability

  • βœ… Single source of truth: Types, configs, and components
  • βœ… Easy updates: Change once, update everywhere
  • βœ… Dependency management: Workspace protocol prevents version drift

Conclusion

This monorepo architecture represents modern best practices for frontend development at scale. By combining Turborepo's build orchestration, pnpm's efficient package management, and a well-organized structure, it provides:

  1. Exceptional Developer Experience: Fast builds, hot reload, type safety
  2. Code Reusability: Shared packages eliminate duplication
  3. Scalability: Easy to add new apps and packages
  4. Maintainability: Consistent patterns and single source of truth
  5. Performance: Optimized builds with caching and parallel execution

Whether you're starting a new project or migrating an existing one, this architecture provides a solid foundation for building modern, scalable frontend applications.

Further Resources


Repository

Check out the full implementation at: [https://github.com/khanhspring/monorepo-ui-starter]

Feel free to use this starter as a foundation for your next project, and contribute improvements back to the community!


Last updated: November 27, 2025
Built with ❀️ for the frontend community

Top comments (0)