development
EMRC CMS
Custom content management system built for EMRC's communications team — handling articles, regulatory documents, podcast episodes, media uploads, and multi-author workflows across the full site.
Role
Full-Stack Dev
Year
2024
Category
development
Tech Stack
The Problem
The EMRC website rebrand created a new problem: a large, high-quality site that the communications team still couldn't manage themselves. Content updates still required developer access to the codebase. With 120+ pages across multiple content types — insights, podcasts, webinars, project case studies, team profiles, job listings — the team needed a purpose-built CMS that matched the structure of their site rather than forcing their content into a generic WordPress model.
The Execution
I built the CMS as a React single-page application with TanStack Query managing all server state, backed by an Express API with MySQL via Sequelize for structured data. Rich text editing is handled by Editor.js, which outputs clean JSON rather than raw HTML — making it straightforward to render consistently on the frontend. Media uploads go through Multer on the server, then to Cloudinary for storage and transformation: images are resized and compressed automatically, PDFs are processed with pdf-lib for compression, and audio files are handled via FFmpeg for format normalization. Redis caches frequently read data like the publications list and team directory. Socket.io pushes real-time notifications to open CMS sessions — when a second editor is viewing the same draft, the first editor sees an indicator immediately.
Editor.js Rich Text Engine
Content is authored in Editor.js, a block-based editor that outputs structured JSON. Each block type — paragraph, heading, image, embed, PDF link — has its own schema, making content rendering on the frontend predictable and consistent.
Cloudinary Media Pipeline
All uploads route through Cloudinary with server-side processing applied on ingest. Images are automatically resized to defined breakpoints, PDFs are compressed without quality loss, and audio files are normalized to a consistent bitrate.
Multi-Author Workflow
Editors draft content, senior editors review it, and admins publish it. Each stage is a distinct status in the database. Socket.io notifies the relevant users when content moves between stages, and activity logs record every state transition.
Real-Time Collaboration Indicators
When two editors open the same content item, Socket.io broadcasts presence information so both see who else is viewing or editing. This prevents conflicting edits without requiring full document locking.
Impact
Technical Notes
Editor.js was the right call for this project, but it required more integration work than expected. Its default output is a JSON array of blocks, and the frontend needed a renderer that could take that JSON and produce consistent HTML without re-introducing the same inconsistencies that raw HTML editing creates. I built a small block renderer on the Next.js side — a switch statement over block types that maps each type to a pre-styled React component. The result is that content authors have a flexible editing experience while the frontend always renders clean, consistently styled output. The Redis caching layer also needed careful invalidation logic: when a content item is published, the cache keys for every listing page that might include it need to be cleared simultaneously, not lazily.
