Building 18 Tools: A Monorepo Architecture That Actually Works

How we structured a 18-tool monorepo with zero shared dependencies, modular ES modules, and a template system that makes new tools take 2 hours, not 2 days.

The Problem: Tool Sprawl

When Neorgon hit 6 tools, we had a problem. Each tool lived in its own git repo, with its own deployment pipeline, its own set of dependencies, and its own way of doing things.

Adding a new tool meant:

  • Copy-pasting a previous tool
  • Hunting for the latest patterns
  • Updating 3 different READMEs
  • Praying you didn't forget the OG image generation

It took half a day of tedious work. We needed a better way.

πŸ’‘ The Requirements

  • βœ… Zero shared dependencies between tools
  • βœ… Independent deploys (no lockstep releases)
  • βœ… Consistent patterns and conventions
  • βœ… 2-hour new tool scaffolding time
  • βœ… Easy to extract tools later if needed

Solution: The Monorepo That Isn't

Most monorepos use shared dependencies and orchestrated builds. We did the opposite: independent everything, centralized decisions.

Directory Structure

Root Structure
Personal/
β”œβ”€β”€ _template/                    # Source of truth for patterns
β”‚   β”œβ”€β”€ index.html               # Single-file template
β”‚   └── app/                     # Modular app template
β”œβ”€β”€ neorgon-site/               # The hub (uses _template)
β”œβ”€β”€ json-builder-site/          # Independent tool
β”œβ”€β”€ presentation-sage/          # Independent tool
└── ...18 tools total

Key insight: Each tool is completely self-contained. They don't import from each other. They don't share node_modules. They're independent apps that happen to live in the same Git repo.

The Template System

Everything flows from _template/. It's not a libraryβ€”it's a living pattern:

Template Rules
// Every tool follows the same structure:
passport-site/
β”œβ”€β”€ index.html          # Main page (80-150 lines)
β”œβ”€β”€ css/
β”‚   └── style.css       # All styles in one file
└── js/                 # Modular ES modules (if needed)
    β”œβ”€β”€ app.js          # Entry point (<50 lines)
    β”œβ”€β”€ state.js        # Shared state
    β”œβ”€β”€ render.js       # DOM rendering
    β”œβ”€β”€ events.js       # Event handlers
    └── utils.js        # Helpers

Single-File vs Modular

We have two templates:

  1. Single-file (_template/index.html) - For simple pages under 1,000 lines
  2. Modular (_template/app/) - For interactive apps over 1,000 lines

No middle ground. Every tool uses one or the other. This eliminates decision fatigue.

The 2-Hour New Tool Formula

Here's exactly how we scaffold a new tool:

⏱️ Step 1: Copy Template (15 minutes)

cp -r _template/app new-tool-site
cd new-tool-site

🎨 Step 2: Customize (45 minutes)

  • Update index.html title/meta
  • Add tool-specific CSS variables
  • Implement core functionality in js/
  • Test locally with python3 -m http.server

πŸ“Έ Step 3: Generate Assets (20 minutes)

  • Create OG image in OG Studio
  • Generate favicon.ico
  • Make tool icon in design tool

🎯 Step 4: Hub Integration (40 minutes)

  • Add card to neorgon-site/index.html
  • Update category in search.js
  • Add preview to previews.js
  • Test search/filter integration

Key Patterns That Make It Work

1. No Build Step

Every tool runs directly from source:

Pure HTML/CSS/JS
# No webpack, no npm install, no compilation
python3 -m http.server 8800
open http://localhost:8800/new-tool/

This means:

  • βœ… Deploy by pushing to GitHub Pages
  • βœ… Debug in browser DevTools (source = actual code)
  • βœ… No build failures
  • βœ… Instant preview on save

2. Zero Shared Dependencies

Contrast this with typical monorepos:

Typical Monorepo (package.json)
{
  "dependencies": {
    "react": "^18.0.0",    // All tools locked to same version
    "lodash": "^4.17.21"    // Shared across everything
  }
}
Neorgon (No package.json)
# Each tool includes libraries directly
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>

# Or vendor them
js/vendor/sortable.min.js

Trade-off: 50KB duplicated across sites vs. 2 hours of dependency hell when upgrading React.

We chose duplication.

3. Consistent State Management

Every tool uses the same pattern:

js/state.js (Every Tool)
// Mutable shared state object
export const state = {
  currentTool: null,
  settings: {
    theme: 'dark',
    sound: true
  }
};

// No Redux, no context providers
// Just import and mutate (with caution)

It's not pure. It's not functional. But it means every tool's state works the same way, and you can debug it by inspecting window._state.

4. Standardized Exports

Every tool exposes the same helpers:

Standard Window API
// Available in every tool's console
window.exportData()      // Download current state
window.importData(json)  // Load previous state
window.reset()          // Reset to defaults
window._state           // Inspect current state

// For hub integration
window._toolReady = true // Signal loaded

What We Avoided

❌ Lerna/NX

We don't need orchestrated builds. Each tool deploys independently.

❌ Shared Components

No @neorgon/ui package. Copy-paste is simpler than versioning.

❌ Microservices

Each tool is a static site. No servers, no Docker, no Kubernetes.

Escape Hatches

Every architectural decision includes an escape route:

πŸšͺ Extracting a Tool

If a tool needs to stand alone:

cd new-tool-site
git init
git remote add origin https://github.com/new/tool
git push -u origin main

No refactoring needed. It's already self-contained.

πŸ”— Adding Shared Logic

If 3+ tools need the same function:

// Copy to each tool first
// If it stabilizes, create js/shared/ in the tool
git mv js/shared-utils.js js/shared/utils.js
// Import from relative path

Only extract when it hurts.

Real Numbers

After 18 tools, here's what the monorepo looks like:

Repository Stats
  • πŸ“ 18 tool directories
  • πŸ“„ 1,200 total files
  • πŸ’Ύ 4.2MB total (including assets)
  • ⚑ Average tool size: 230KB
  • πŸš€ New tool time: 1.8 hours average
  • πŸ› Shared bugs: ~5% (mostly CSP issues)

The Template Checklist

Before a tool graduates from template to production, it must:

  • βœ… Work with python3 -m http.server (no build)
  • βœ… Pass Lighthouse audit (95+ performance)
  • βœ… Support prefers-reduced-motion
  • βœ… Load in 2 seconds on 3G
  • βœ… Have OG image, favicon, and meta tags
  • βœ… Include console helpers (exportData, reset)
  • βœ… Hub card added with correct category

Lessons Learned

1. Start Simple, Stay Simple

We almost used Turborepo. Glad we didn't. We don't need caching or pipelinesβ€”cp -r is faster.

2. Duplication is Sometimes Better

Those 50KB of duplicated libraries? Worth 100 hours of dependency management.

3. Documentation is the Real Dependency

The template isn't the source of truthβ€”CLAUDE.md is. It explains why we made each choice.

4. Extract Last, Not First

We waited until we had 12 tools before creating _template/. The pattern emerged from repetition.

When to Use This Architecture

βœ… Perfect Fit For:

  • Many small, unrelated projects
  • Rapid prototyping
  • Learning/experimentation repos
  • Independent deployment requirements

❌ Wrong Fit For:

  • Large, interconnected applications
  • Shared business logic
  • Coordinated releases
  • Enterprise teams