Architecture
CleanTextLab Architecture Documentation
Version: 1.7.0 Last Updated: January 5, 2026 Maintainer: Tyson K.
Table of Contents
- System Overview
- Architecture Principles
- Folder Structure
- Data Flow
- Key Components
- State Management
- Authentication & Authorization
- API Architecture
- Tool Architecture Pattern
- Feature Flags & Entitlements
- Performance & Optimization
- Security Model
- Deployment
- Decision Log
System Overview
CleanTextLab is a Next.js 15 application providing 29 text processing tools (including a workflow builder), API access, and Pro features. The application follows a client-first architecture where all text processing happens in the browser, with optional cloud sync for Pro users.
Core Value Proposition
- Privacy-First: All processing happens client-side (browser)
- Developer-Friendly: REST API for automation (
/api/v1/run) - Pro Features: Cloud history sync, batch processing, API keys
- Workflow Automation: Chain multiple tools together
- Multi-Language: 9 locales (en, es, fr, pt, de, hi, ar, sw, zu)
Technology Stack
┌─────────────────────────────────────────────────┐
│ Frontend │
├─────────────────────────────────────────────────┤
│ Next.js 15.5.9 (App Router) │
│ React 19 + TypeScript 5.x │
│ Tailwind CSS v4 (CSS Variables Theme System) │
│ Lucide Icons │
│ React Markdown (Docs rendering) │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Backend │
├─────────────────────────────────────────────────┤
│ Next.js API Routes │
│ Clerk (Authentication) │
│ Upstash Redis (Cloud Storage) │
│ Stripe (Payments) │
│ Google Analytics 4 │
│ Microsoft Clarity (optional) │
│ Resend (transactional email) │
│ AI SDK (Google/OpenAI) for smart routing │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Infrastructure │
├─────────────────────────────────────────────────┤
│ Vercel (Hosting + Edge Functions) │
│ GitHub (Version Control) │
│ Playwright (E2E Testing) │
│ MCP CLI (bin/mcp.js) │
└─────────────────────────────────────────────────┘
Architecture Principles
1. Client-First Processing
All text transformations happen in the browser using Web APIs and custom JavaScript logic. Nothing is sent to the server unless explicitly requested (sharing, cloud sync).
Rationale: Privacy, speed, and cost efficiency.
2. Progressive Enhancement
Free users get full tool functionality. Pro users get enhancements:
- Cloud history sync (encrypted)
- API access (5,000 req/day)
- Batch processing (5,000 lines vs 100 free)
- Priority support
3. Composable Tools
Each tool is a self-contained React component with:
- Input state management
- Processing logic
- Output rendering
- History tracking
- Keyboard shortcuts
Tools can be chained together via the Workflow Builder.
4. API-First for Automation
The /api/v1/run endpoint allows chaining multiple tools in a single API call:
POST /api/v1/run
{
"input": "messy text",
"steps": ["trim-lines", "upper-case", "dedupe"]
}
5. Themeable by Design
11 themes (Dark, Light, Ocean, Neon, etc.) using CSS variables:
--bg,--surface,--text-primary,--accent- All components use these variables
- Theme state persists in localStorage
Folder Structure
cleantextlab/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── api/ # Backend API routes
│ │ │ ├── v1/ # Public API (requires API key)
│ │ │ │ ├── run/ # Workflow runner endpoint
│ │ │ │ ├── sanitize/ # URL sanitizer endpoint
│ │ │ │ └── sort-dedupe/ # Sort & dedupe endpoint
│ │ │ ├── stripe/ # Payment webhooks
│ │ │ ├── entitlement/ # Pro plan checks
│ │ │ ├── history/ # Cloud history sync
│ │ │ ├── share/ # Share link generation
│ │ │ └── settings/ # User preferences sync
│ │ ├── tools/ # Tool pages (29 tools)
│ │ │ ├── json-formatter/
│ │ │ ├── sort-remove-duplicates/
│ │ │ ├── workflows/ # Workflow builder
│ │ │ └── [tool-name]/
│ │ ├── docs/ # Documentation pages
│ │ │ ├── api/ # Public API docs
│ │ │ └── cloud-history-sync/
│ │ ├── ar/ # Arabic localization
│ │ ├── de/ # German localization
│ │ ├── es/ # Spanish localization
│ │ ├── fr/ # French localization
│ │ ├── hi/ # Hindi localization
│ │ ├── pt/ # Portuguese localization
│ │ ├── sw/ # Swahili localization
│ │ ├── zu/ # Zulu localization
│ │ └── (pages)/ # Marketing/support pages
│ │
│ ├── components/
│ │ ├── tools/ # 29 tool implementations
│ │ │ ├── JsonFormatterTool.tsx
│ │ │ ├── SortRemoveDuplicatesTool.tsx
│ │ │ └── [ToolName]Tool.tsx
│ │ ├── GlobalNav.tsx # Top navigation
│ │ ├── CommandMenu.tsx # Cmd+K search
│ │ ├── MagicInput.tsx # Smart paste detection
│ │ ├── ShareActions.tsx # Share/export dropdown
│ │ ├── ToolHistoryDropdown.tsx
│ │ ├── PresetManager.tsx # Workflow presets
│ │ └── (shared components)/
│ │
│ ├── hooks/ # Custom React hooks
│ │ ├── useUndoRedo.ts # History management (50-item stack)
│ │ ├── useToolHistory.ts # Tool usage tracking
│ │ ├── useShareableTool.ts # Share link generation
│ │ ├── useEntitlement.ts # Pro plan checks
│ │ ├── useMagicPayload.ts # Smart paste integration
│ │ ├── useKeyboardShortcuts.ts
│ │ ├── useSettingsSync.ts # Cloud settings sync
│ │ └── useDebounce.ts
│ │
│ ├── lib/ # Utilities & business logic
│ │ ├── agents/
│ │ │ └── contentDetector.ts # Smart paste detection
│ │ ├── server/
│ │ │ ├── storage.ts # Upstash Redis client
│ │ │ └── apiKeyRotation.ts
│ │ ├── analytics.ts # GA4 tracking
│ │ ├── encryption.ts # AES-256-GCM for cloud sync
│ │ ├── workflowEngine.ts # Tool chaining logic
│ │ ├── textTransforms.ts # Core text processing
│ │ ├── toolMetadata.ts # Tool metadata (SEO + docs)
│ │ ├── toolsRegistry.ts # Tool catalog for UI
│ │ ├── pdfExport.ts # Client-side PDF generation
│ │ ├── csvExport.ts # CSV generation
│ │ └── userPreferences.ts # Theme/settings management
│ │
│ └── workers/ # Web Workers (future)
│ └── textProcessor.worker.ts
│
├── e2e/ # Playwright E2E tests
│ ├── api-smoke.spec.ts
│ ├── comprehensive-tools.spec.ts
│ ├── magic-input.spec.ts
│ └── (50+ test files)
│
├── public/ # Static assets
│ ├── images/
│ └── robots.txt
│
└── docs/ # Documentation (you are here)
├── ARCHITECTURE.md ← This file
├── TOOL_REGISTRY.md
├── PATTERNS.md
├── API_REFERENCE.md
└── CHANGELOG.md
Key Directories Explained
src/app/ - Next.js App Router file-based routing
src/components/tools/ - Each tool is a self-contained component
src/hooks/ - Reusable React hooks (business logic extraction)
src/lib/ - Pure functions, utilities, and server-side code
e2e/ - End-to-end tests for critical user flows
Data Flow
Client-Side Processing Flow
┌─────────────┐
│ User │
│ Input │ (types text)
└──────┬──────┘
│
▼
┌─────────────────────────────────────┐
│ Tool Component │
│ - Input state (useUndoRedo) │
│ - Processing logic (pure function) │
│ - Output rendering │
└──────┬──────────────────────────────┘
│
├───► [Tool History] → localStorage
│
├───► [Analytics] → GA4
│
└───► [Share/Export]
├─► Cloud Share → Upstash Redis
├─► PDF Export → Client-side PDF.js
└─► CSV Export → Blob download
Cloud Sync Flow (Pro Users)
┌─────────────┐
│ User │
│ (Pro Plan) │
└──────┬──────┘
│
▼
┌─────────────────────────────────┐
│ Tool Usage │
│ - Input/Output generated │
└──────┬──────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ useToolHistory Hook │
│ - Saves to localStorage │
│ - Debounced sync to cloud │
└──────┬──────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ /api/history (POST) │
│ - Encrypts data (AES-256-GCM) │
│ - Stores in Upstash Redis │
│ - TTL: 90 days │
└─────────────────────────────────┘
API Request Flow
┌─────────────┐
│ External │
│ Client │ (curl, n8n, Zapier)
└──────┬──────┘
│ POST /api/v1/run
│ x-api-key: abc123
▼
┌─────────────────────────────────┐
│ Middleware │
│ - Rate limiting (10 req/min) │
│ - API key validation │
└──────┬──────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ /api/v1/run Handler │
│ - Parse steps array │
│ - Execute workflow │
│ - Return result │
└──────┬──────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ workflowEngine.ts │
│ - Chain tool executors │
│ - Run transforms sequentially │
└─────────────────────────────────┘
Key Components
1. Tool Component Pattern
Every tool follows this structure:
// Example: JsonFormatterTool.tsx
export function JsonFormatterTool() {
// 1. State management with undo/redo
const { state: input, setState: setInput, undo, redo, canUndo, canRedo }
= useUndoRedo("");
// 2. Tool-specific processing logic
const output = useMemo(() => processInput(input), [input]);
// 3. History tracking
const { history, clearHistory } = useToolHistory(TOOL_SLUG, () => ({
input,
output,
metadata: { /* tool-specific data */ }
}));
// 4. Share functionality
const { handleShare, shareStatus } = useShareableTool({
toolSlug: TOOL_SLUG,
sharePayload: output || input
});
// 5. Keyboard shortcuts
useKeyboardShortcuts([
{ key: "Enter", ctrlOrCmd: true, action: handleProcess },
{ key: "z", ctrlOrCmd: true, action: undo }
]);
// 6. Render: Input → Actions → Output
return (
<div>
<textarea value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={handleProcess}>Process</button>
<textarea value={output} readOnly />
</div>
);
}
2. MagicInput Component
Smart paste detection that suggests tools based on content:
// lib/agents/contentDetector.ts
export function detectContent(text: string): {
type: string;
confidence: number;
suggestedTool: string;
}
// Detects:
// - JSON → json-formatter
// - URLs → sanitize-url
// - Phone numbers → phone-number-formatter
// - Base64 → base64-encode-decode
// - Cron expressions → cron-generator
// - SQL → sql-formatter
3. Workflow Builder
Allows chaining multiple tools:
// User creates workflow:
[
{ tool: "trim-lines", config: {} },
{ tool: "dedupe", config: { ignoreCase: true } },
{ tool: "sort", config: { direction: "asc" } }
]
// Executed via workflowEngine.ts
const result = await executeWorkflow(input, steps);
4. Share System
Two modes:
- URL Encoding (small payloads):
?shared=base64EncodedData - Cloud Storage (large payloads):
/share/abc123→ Redis
// useShareableTool.ts
if (payload.length < 1000) {
// Use URL param
return `${baseUrl}?shared=${btoa(payload)}`;
} else {
// Store in Redis
const id = generateId();
await redis.set(`share:${id}`, payload, { ex: 2592000 }); // 30 days
return `${baseUrl}/share/${id}`;
}
State Management
Local State (React)
- Component state:
useStatefor ephemeral UI state - Form state:
useUndoRedofor input fields (50-item history) - Derived state:
useMemofor computed outputs
Persistent State (localStorage)
{
"user_preferences": {
"theme": "dark",
"compactMode": false,
"historyLimit": 50
},
"tool_history_json-formatter": [...], // Last 50 uses
"recent_tools": ["json-formatter", "url-sanitizer"],
"workflow_presets": [...]
}
Cloud State (Upstash Redis) - Pro Only
// Key pattern: userId:tool:timestamp
"user_abc123:json-formatter:1702500000": {
input: "...",
output: "...",
metadata: {},
encrypted: true // AES-256-GCM
}
Global State (React Context)
- Clerk Auth Context: User session, Pro plan status
- Theme Context: Current theme, theme switcher
Authentication & Authorization
Authentication Provider: Clerk
// Middleware checks authentication on protected routes
export default clerkMiddleware((auth, req) => {
const { userId, sessionClaims } = auth();
// Public routes: /tools/*, /
// Protected routes: /settings, /api/keys, /history
if (isProtectedRoute(req.nextUrl.pathname) && !userId) {
return redirectToSignIn({ returnBackUrl: req.url });
}
});
Authorization: Plan-Based Entitlements
// useEntitlement.ts
const { plan, loading } = useEntitlement();
if (plan === "pro") {
// Allow API access
// Allow batch processing (5,000 lines)
// Enable cloud history sync
} else {
// Free tier: 100 lines, no API, no cloud sync
}
API Key Management
// /api/entitlement/route.ts - Generates API keys
// /api/entitlement/rotate/route.ts - Rotates API keys
// /api/keys/route.ts - Lists user's API keys (email only)
// Stored in Redis:
"api_key:sk_live_abc123": {
userId: "user_xyz",
plan: "pro",
created: 1702500000,
rateLimit: 5000 // req/day
}
API Architecture
Public API: /api/v1/*
Authentication: API key via x-api-key header
Rate Limiting: Pro only - 5,000/day, enforced per key
Response Format: JSON
Endpoints
POST /api/v1/run - Workflow Runner
{
"input": "text to process",
"steps": ["trim-lines", "upper-case", "dedupe"]
}
→ { "result": "PROCESSED TEXT" }
MCP Server (Agents):
- MCP CLI (
cleantextlab-mcp) calls/api/v1/runwithsteps: [toolSlug]. - API access requires Pro subscription (5,000 calls/day).
POST /api/v1/sanitize - URL Sanitizer
{
"urls": ["https://example.com?utm_source=..."],
"removeParams": ["utm_source", "fbclid"]
}
→ { "sanitized": ["https://example.com"] }
POST /api/v1/sort-dedupe - Sort & Deduplicate
{
"lines": ["apple", "banana", "apple"],
"sort": true,
"dedupe": true
}
→ { "result": ["apple", "banana"] }
Internal API: /api/*
POST /api/share - Generate share link
GET /api/share/[id] - Retrieve shared content
POST /api/history - Sync tool history (Pro)
GET /api/history/all - Fetch all history (Pro)
POST /api/settings - Sync user preferences (Pro)
POST /api/checkout - Stripe checkout session
POST /api/stripe/webhook - Stripe events
Tool Architecture Pattern
Tool Component Lifecycle
- Mount: Load from localStorage or URL params
- User Input: Track changes via
useUndoRedo - Processing: Run transformation (client-side)
- Output: Display result with copy/share actions
- History: Auto-save to localStorage (debounced)
- Cloud Sync (Pro): Encrypt and upload to Redis
Standard Hooks Used by Tools
// Undo/Redo with Cmd+Z
const { state, setState, undo, redo, canUndo, canRedo } = useUndoRedo("");
// Tool history tracking
const { history, clearHistory } = useToolHistory(toolSlug, getEntry);
// Share link generation
const { handleShare, shareStatus, lastShareUrl } = useShareableTool({
toolSlug,
sharePayload
});
// Keyboard shortcuts
useKeyboardShortcuts([
{ key: "z", ctrlOrCmd: true, action: undo },
{ key: "c", ctrlOrCmd: true, action: copyOutput }
]);
// Pro plan detection
const { plan, isPro } = useEntitlement();
// Smart paste integration
useMagicPayload((payload) => setInput(payload));
Feature Flags & Entitlements
Pro Features Gating
// Check plan status
const { plan, loading } = useEntitlement();
// Conditional rendering
{isPro ? (
<BatchProcessor limit={5000} />
) : (
<UpgradePrompt feature="Batch processing" />
)}
// Conditional limits
const MAX_LINES = isPro ? 5000 : 100;
Feature Flags (Future)
Currently hardcoded, but ready for remote config:
// lib/featureFlags.ts (future)
export const FEATURES = {
CLOUD_SYNC: true,
WORKFLOW_BUILDER: true,
API_V2: false, // Not released yet
AI_SUGGESTIONS: false
};
Performance & Optimization
Client-Side Optimizations
- Code Splitting: Each tool lazy-loaded via App Router
- Memoization: Heavy computations cached with
useMemo - Debouncing: History sync debounced (500ms)
- Web Workers (future): Large text processing in worker threads
Server-Side Optimizations
- Edge Functions: API routes run on Vercel Edge (low latency)
- Redis Caching: Share links cached for 30 days
- Rate Limiting: Prevents abuse (10 req/min public API)
- CDN: Static assets served via Vercel CDN
Bundle Size
- Main Bundle: ~102KB (shared chunks)
- Tool Bundle: ~140KB average per tool
- Total Initial Load: ~250KB (gzipped)
Security Model
Data Privacy
- Client-Side Processing: Text never sent to server (except for sharing)
- E2E Encryption: Cloud sync uses AES-256-GCM
- No Tracking: Analytics don't capture user text
- HTTPS Only: All connections encrypted
API Security
- Rate Limiting: 10 req/min per IP, 5,000 req/day per API key
- API Key Rotation: Users can rotate keys via
/api/entitlement/rotate - Input Validation: All API inputs sanitized
- CORS: Restricted to allowed origins
Authentication Security
- Clerk Auth: Industry-standard OAuth 2.0
- Session Management: Secure HTTP-only cookies
- CSRF Protection: Built-in Next.js CSRF tokens
Deployment
Production Environment
- Platform: Vercel
- URL: https://cleantextlab.com
- Deployment: Auto-deploy on
mainbranch push - Environment: Production
Environment Variables
# Authentication
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=...
CLERK_SECRET_KEY=...
# Storage
UPSTASH_REDIS_REST_URL=...
UPSTASH_REDIS_REST_TOKEN=...
# Payments
STRIPE_SECRET_KEY=...
STRIPE_WEBHOOK_SECRET=...
# Analytics (consent-gated)
NEXT_PUBLIC_GA_ID=G-...
CI/CD Pipeline
┌─────────────┐
│ Git Push │
│ to main │
└──────┬──────┘
│
▼
┌─────────────────────┐
│ GitHub Actions │
│ - Run E2E tests │ (optional)
│ - Type check │
└──────┬──────────────┘
│
▼
┌─────────────────────┐
│ Vercel Build │
│ - npm run build │
│ - Generate static │
│ - Deploy edge fns │
└──────┬──────────────┘
│
▼
┌─────────────────────┐
│ Production Live │
│ cleantextlab.com │
└─────────────────────┘
Decision Log
Why Next.js App Router?
- File-based routing: Scales to 30+ tools easily
- React Server Components: Faster initial loads
- Edge runtime: Low-latency API routes
- Built-in optimizations: Image optimization, font optimization
Why Client-Side Processing?
- Privacy: User data never leaves browser
- Cost: No server compute costs
- Speed: Instant processing (no network round-trip)
- Scalability: Infinite scale (client resources)
Why Upstash Redis?
- Serverless: Pay-per-request pricing
- Global: Low latency worldwide
- Simple: HTTP-based API (no TCP connections)
- Free Tier: 10,000 commands/day
Why Clerk for Auth?
- Developer Experience: Easy integration
- Pre-built UI: Sign-in/sign-up components
- Session Management: Automatic token refresh
- Pricing: Free tier includes 5,000 MAU
Why Stripe for Payments?
- Industry Standard: Trusted payment processor
- Developer-Friendly: Excellent API and docs
- Subscription Management: Built-in recurring billing
- Security: PCI compliant
Why TypeScript?
- Type Safety: Catch bugs at compile time
- Autocomplete: Better DX in VS Code
- Refactoring: Safe large-scale changes
- Documentation: Types are self-documenting
Maintenance Notes
Adding a New Tool
- Create tool component:
src/components/tools/NewTool.tsx - Create tool page:
src/app/tools/new-tool/page.tsx - Add metadata:
src/lib/toolMetadata.ts - Add to workflow engine:
src/lib/workflowOperations.ts - Update TOOL_REGISTRY.md
- Write E2E test:
e2e/new-tool.spec.ts
Updating Architecture
When making architectural changes:
- Update this file (ARCHITECTURE.md)
- Update CHANGELOG.md with rationale
- Run full E2E test suite
- Document breaking changes
Code Review Checklist
- Uses
useUndoRedofor input state - Follows tool component pattern
- Includes keyboard shortcuts
- Tracks analytics events
- Has E2E test coverage
- Updates TOOL_REGISTRY.md
- Client-side processing only
Future Roadmap
Phase 1: Q1 2025
- Product Hunt launch
- SEO optimization (sitemap submission)
- JavaScript/Python SDKs
Phase 2: Q2 2025
- AI-powered text suggestions
- More automation platform integrations (n8n, Make, Zapier)
- VS Code extension
Phase 3: Q3 2025
- Team collaboration features
- Custom tool builder
- Mobile app (React Native)
Document Owner: Tyson K. Last Review: January 5, 2026 Next Review: March 1, 2026 Feedback: Issues at https://github.com/gravitasse/cleantextlab/issues