The Tech Stack
For the technically inclined, I prepared the list of juicy details.
Infrastructure (AWS CDK)
- DynamoDB — Single-table design, pay-per-request, 4 GSIs (Domain, Type, Status, Slug), point-in-time recovery
- Lambda — Node.js 22, ARM64, 100+ handlers across 30 modules, esbuild bundled
- API Gateway HTTP v2 — Lambda authorizer, rate limiting (50 req/sec, burst 100), strict CORS
- CloudFront — 3 distributions (admin SPA, renderer, public assets CDN)
- S3 — 3 buckets (public assets, private resources with presigned URLs, renderer static + ISR warm cache)
- Cognito — 2 user pools: admin (invite-only, role/tenantId attributes) + public (self-signup, Google OAuth per tenant)
- SES — Contact forms, order confirmations, form notifications, Cognito invite emails
- EventBridge — Custom bus, all mutations publish audit events → worker Lambda → DynamoDB (with DLQ)
- Secrets Manager — Master API key + NextAuth secret
- Route 53 + ACM — DNS for root, wildcard, admin.*, api.*, tenant domains; regional + global certs
- CDK stacks: Main + 2 nested (Commerce, Engagement) to stay under CloudFormation's 500-resource limit
Renderer (Public Sites)
- Next.js 16 (React 19) — deployed via OpenNext 3 to Lambda
- Tailwind CSS v4 with PostCSS plugin (no traditional config file)
- NextAuth.js 4 — Google OAuth, dynamically configured per tenant from DynamoDB
- Middleware — tenant routing (domain → X-Forwarded-Host → rewrite), preview mode, referral tracking
- S3 warm cache — ISR pages cached in S3 (_cache/ prefix) for fast revalidation
- DOMPurify for HTML sanitization
- Direct DynamoDB reads — no API calls, server-side only
- CloudFront Function — preserves X-Forwarded-Host header for multi-tenant routing
Admin Panel (Control Panel SPA)
- React 19 + Vite 7
- shadcn/ui (Radix UI primitives: Dialog, Select, Dropdown, Label, Slot)
- Tailwind CSS v4 + tailwindcss-animate + @tailwindcss/typography
- Tiptap 3 — block-based editor with 19 custom plugins (split admin/render entry points)
- React Router 7 — client-side routing (39 pages)
- AWS Amplify 6 — Cognito auth integration
- React Flow + Dagre — content graph visualization
- lucide-react — icons
- Deployed to S3 + CloudFront with SPA fallback routing
Backend (Lambda Handlers)
- Node.js 22 (ARM64, esbuild, ESM with .js extensions)
- AWS SDK v3 — DynamoDB, Cognito, SES, EventBridge, S3, Secrets Manager
- aws-jwt-verify — token validation in Lambda authorizer
- fast-xml-parser — WordPress WXR import
- Vitest — tests against real staging DynamoDB
Shared Packages
- @amodx/shared — Zod 4 schemas, TypeScript types, country packs (single source of truth)
- @amodx/plugins — 19 block plugins (hero, pricing, image, contact, video, leadMagnet, cta, features, testimonials, columns, table, html, faq, postGrid, carousel, codeBlock, reviewsCarousel, categoryShowcase, markdown), highlight.js + marked for syntax highlighting
MCP Server (Claude Integration)
- MCP SDK — StdioServerTransport for Claude Desktop
- Playwright — browser automation (Chromium)
- Cheerio + Axios — web scraping (careful with that!)
- Full content CRUD + block schema knowledge embedded
Key Patterns
- Multi-tenancy: x-tenant-id on every request, PK-prefix isolation, never cross-tenant queries
- Single-table DynamoDB: adjacency lists (CATPROD#, CUSTORDER#), dual-write patterns (COUPONCODE#, FORMSLUG#), atomic counters
- Event-driven audit: all mutations → EventBridge → async worker (decoupled, with DLQ)
- Content versioning: automatic snapshots on every update, restore API
- Plugin architecture: split entry (admin.ts for Tiptap, render.ts for SSR-safe React), no cross-imports
- Country packs: 3-tier merge (English defaults → country pack → admin overrides)
- npm workspaces: build order shared → plugins → backend/admin/renderer
The repo: https://github.com/andreirx/amodx