Overview
The client's teams designed architectural floor plans in a slow, single-user desktop program with a rigid file format. We rebuilt it as a web-based editor: a custom drawing engine renders plans as SVG in the browser and rasterises them to PNG/JPEG with Canvas, users place components from a large catalogue across multiple floors, and every plan round-trips losslessly through the client's XML format — all in a Turborepo monorepo with a DDD/CQRS NestJS API and an admin suite for master data.
The problem
- A legacy desktop tool — slow, single-user, hard to distribute or update.
- A rigid file format — plans had to round-trip through the client's XML schema without losing a field.
- A huge component catalogue — 1,100+ architectural components, each with precise geometry and default properties, had to render and stay editable at interactive speed.
- Session integrity on shared machines — a stale login must not linger; a new sign-in has to invalidate older sessions immediately.
- Heavy plans — reference images saved inside a plan made files large and slow to open.
The hardest problems
- A drawing engine, not a form. The core is a real 2D CAD surface: hit-testing, grid snapping and alignment, a command stack with undo/redo that works across floors, rotation/flip, and multi-floor layers — kept smooth by owning the render loop and keeping React out of it.
- 1,100+ components, each correct. Every component carries its own default properties (units, area, colours, patterns) and placement behaviour (e.g. wall-mounted fittings that follow their host wall). The catalogue is the product's surface area.
- Faithful raster export. Flattening a vector plan and its external images into a single, pixel-accurate PNG/JPEG on a Canvas — without tripping the browser's tainted-canvas security limits.
- Lossless legacy XML. Import and export must round-trip the client's schema with zero data loss, so the format can evolve without breaking old plans.
- Real-time session integrity. The instant a user signs in again, every other session for that user must be logged out — across tabs and devices — without polling.
Architecture
The editor is a React shell around a framework-agnostic drawing engine. The engine renders the live editing surface as an SVG scene and mutates it imperatively, so React never re-renders on each edit — drawing stays responsive as a plan grows. Canvas 2D owns the raster side: exporting plans to PNG/JPEG, generating thumbnails, and measuring real glyph widths for pixel-accurate labels. The whole system is a Turborepo monorepo — the engine (geometry, snapping, undo/redo across floors, rotation/flip, multi-floor layers) and the domain core are one shared package consumed by both the client and the backend, so there is a single source of truth for geometry and types. The NestJS API is organised with DDD + CQRS; XML import/export is an isolated, lossless module.
client — React shell · UI · property panels · toolbars
├── SVG engine live editor · geometry · snap · undo/redo · multi-floor
└── Canvas 2D PNG/JPEG export · thumbnails · glyph metrics
↕ Turborepo monorepo — shared engine + domain core ↕
server
├── NestJS · DDD/CQRS drawings · master data · admin
├── Auth + SSE JWT · single active session · real-time logout
├── Asset pipeline private S3 · signed URLs · CDN (CloudFront)
└── XML import/export isolated, lossless
↕
data
└── PostgreSQL plans · master data · S3 assets
Auth & real-time session security
Auth is JWT-based, but the interesting part is enforcing a single active session in real time. Each signed-in client opens a Server-Sent Events (SSE) stream, tagged with the session's token version:
- When a user signs in again (new device, new tab), the server bumps the token version and notifies every older stream for that user.
- Those streams receive a
session-replacedevent and log themselves out instantly — no polling, no stale sessions left behind on shared workstations. - A 30-second heartbeat keeps each stream alive and lets the client detect a dropped connection.
- Within one browser, a cross-tab BroadcastChannel coordinates all tabs, so sign-in and sign-out are consistent everywhere at once.
SSE (one-way server→client) was the right primitive: the only real-time need is the server pushing session events, so a full WebSocket layer would have been more infrastructure for no extra capability.
Asset pipeline (private by default)
Plan assets (reference images, thumbnails) are kept private — the storage bucket is never public. The backend signs each object URL at response time, so an API response hands the client a short-lived link instead of a durable public URL; the link expires and the object stays protected.
Two performance moves sit alongside it:
- Externalising embedded images. Plans used to carry reference images inline (base64) inside the saved content, which bloated payloads and slowed loads. Moving them to object-storage references cuts the transferred size dramatically and keeps the plan document lightweight.
- A CDN in front of storage. Signed assets are served through a CDN (CloudFront), so images are cached at the edge for low-latency delivery and reduced load on the origin.
Key decisions & trade-offs
- SVG for editing, Canvas for output. The live surface is SVG — crisp at any zoom, easy to hit-test and mutate imperatively without re-rendering React. Canvas 2D does what SVG is poor at: flattening a plan and its images into PNG/JPEG for export and thumbnails, and measuring glyph widths for label layout.
- A Turborepo monorepo. The drawing engine and domain types are one framework-agnostic package shared by both the React client and the NestJS backend — one source of truth for geometry, no drift between front and back.
- Real-time over SSE, not WebSocket. The only real-time need is one-way session events, so SSE plus a heartbeat and a cross-tab channel is simpler and cheaper than a socket layer.
- Private assets, signed on demand. Buckets stay private and links are signed per response — security by default, with CDN (CloudFront) caching in front for low-latency delivery.
Results
- 1,100+ — component types in the catalogue
- SVG + Canvas — vector editor with pixel-accurate PNG/JPEG export
- Undo / redo — full command history across floors
- Real-time — single-session security enforced over SSE
- 12 — engineers led as Technical Leader