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
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:
// 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:
- Single-file (
_template/index.html) - For simple pages under 1,000 lines - 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.htmltitle/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:
# 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:
{
"dependencies": {
"react": "^18.0.0", // All tools locked to same version
"lodash": "^4.17.21" // Shared across everything
}
}
# 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:
// 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:
// 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:
- π 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