Product Case Study
Rendomat is a full-stack video platform I designed and built solo: content structuring with Claude, a custom timeline editor, per-scene cached rendering, and multi-format export with After Effects interop. Strategy, systems architecture, and interface design in one product.
Scene Types
14
Text, charts, images, equations
Transitions
22
Crossfade to iris-close
Animation Presets
15
Minimal to cinematic
Export Targets
5
16:9, 1:1, 9:16, square, AE
Cloud Render
~60s
Lambda fan-out (in progress) vs. 3-5 min local
Auth Providers
2
Google + GitHub OAuth
The Problem
Content teams produce great material. But the path from written content to produced video requires tools they don't have, skills they can't hire for, and timelines they can't afford.
Agency videos take 2-4 weeks at $2,000-10,000 per minute. Internal teams using After Effects spend 6-8 hours per video.
Professional NLEs require months of training. Simplified tools lack multi-format export and fine-grained control over timing.
Each platform demands different aspect ratios. A single video concept becomes 3-5 separate production jobs.
The Approach
I evaluated Premiere/AE (powerful but not programmable), template platforms like Canva (easy but ceiling is low), and Remotion (code-based video with React). Remotion was the right abstraction: scenes become React components, themes become data, and rendering is automated.
But Remotion has no visual editor. It's a rendering engine, not a product. I needed to build everything else: the database, the API, the timeline editor, the caching layer, the content pipeline, the export system. That's what this case study is about.
What Remotion gives me
Frame-by-frame rendering via headless Chrome. Full web platform access (CSS, SVG, canvas). Component model for scenes. Automated MP4 output via FFmpeg.
What I built around it
Database + REST API, custom timeline editor, per-scene caching, Claude content pipeline, transition system, audio/video muxing, multi-format export, and AE interop.
Strategic Positioning
The video creation market bifurcates into easy-but-limited templates and powerful-but-slow professional tools. Rendomat is positioned in the gap: Claude-assisted structuring with real editing control and a professional export path.
Canva / Lumen5
Template-first
Strength
Fast, zero learning curve
Gap
Low ceiling: no timeline, no fine-grained control, no AE export. Output looks generic.
Premiere / After Effects
Professional NLE
Strength
Unlimited creative control
Gap
6-8 hour production cycles. No content assistance. Requires trained operators.
Descript / Synthesia
AI-native video
Strength
Script-to-video, avatar generation
Gap
Talking-head focused. Limited scene types. No data visualization or custom animation.
Rendomat
Structured video from documents
Strength
Claude structures content → visual timeline → multi-format export + AE interop + cloud rendering
Current limitation
No real-time collaboration yet.
Strategic Decisions
Four decisions that go beyond “what should the UI look like”. Each one is a business-level bet about where value accrues.
Build vs. Buy
Every timeline library I evaluated (react-timeline-editor, Plyr, Video.js) assumed a single-track model. Rendomat needs four parallel tracks (scenes, transitions, audio, video) with mutual-exclusive selection, frame-precise positioning, and drag-to-reorder. Building from scratch took 3 weeks longer, but the UX advantage compounds: every editing interaction maps directly to the data model with zero translation layer.
Outcome
4-track timeline with sub-frame precision, 0 abstraction leaks from third-party libraries
Positioning
Template tools are fast but creatively constrained. Professional NLEs are powerful but slow. Rendomat occupies the middle: Claude handles the 80% (content structuring, scene selection, initial layout), then a real timeline editor gives fine-grained control over the remaining 20%. The After Effects export bridge means power users never feel trapped.
Outcome
15-minute document-to-video vs. 6-8 hours in traditional tools
Moat
Each scene type (bar chart, equation, spotlights, image gallery) is a self-contained React component. Adding a new type takes one component file and one database seed entry (no plugin architecture, no SDK). This means the scene library grows cheaply, and each new type increases the range of documents Claude can handle. The system gets more capable without getting more complex.
Outcome
14 scene types, linear cost to add more, exponential content coverage
Go-to-Market
The client entity isn't just organization. It's the unit of customization. Each client has default personas, behavior overrides, themes, and cached research. This architecture naturally supports an agency model: one Rendomat instance serves multiple client brands with isolated visual identities. White-label capability is a data model decision, not a feature bolt-on.
Outcome
Multi-tenant by default, per-client theming and generation behavior
Platform
Video editors are desktop apps for a reason: large file I/O, native codec access, and GPU-bound rendering don't fit the browser sandbox. Rather than rewriting the stack, Electron wraps the existing Express + Next.js + Remotion architecture: the main process runs the backend server, the renderer shows the UI, and FFmpeg/Chrome binaries ship bundled. One codebase serves both web deployment and native desktop. The migration cost is one file (electron/main.js); the architecture was already structured for it because every path is relative and the frontend connects to the backend via localhost REST.
Outcome
Single-click desktop app, zero architecture changes, same codebase for web and native
Technical Architecture
The system is split into a Next.js frontend for editing, an Express backend for data and orchestration, and a Remotion engine for rendering. Each layer is independently responsible for its domain.
Full Stack
Engineering Decisions
Six core systems that make the platform work. Each involved architectural trade-offs. I chose simplicity and directness over abstraction wherever possible.
Each scene type (text, bar chart, quote, equation, image grid) is a self-contained React component with typed props. Adding a new scene type means writing one component (no plugins, no DSL). Remotion renders each frame via headless Chrome, giving full access to CSS, SVG, canvas, and WebGL.
14 scene types, each with typed SceneData props and frame-range-aware animation
Rendering video is slow. A seven-scene video takes 2-3 minutes cold. The caching system hashes each scene's data and frame range with SHA256. Before rendering, it checks the cache. Only changed scenes re-render. The final video is stitched from cached clips using FFmpeg. Editing one scene in a seven-scene video drops render time from minutes to seconds.
SHA256 content hashing, FFmpeg concat, sub-5-second feedback loops
The multi-track timeline is built from scratch, no library. Scenes, transitions, audio clips, and video clips live on separate tracks with independent selection, drag-to-reorder, resize-to-adjust-duration, and snap-to-grid. The timeline hook manages all state and converts between frames and pixels for precise positioning.
4 tracks, drag/resize/snap, frame-to-pixel conversion, mutual-exclusive selection
Transitions are rendered as separate clips between scene snapshots, then stitched in. This keeps them independent of scene caching. Changing a transition doesn't invalidate scenes on either side. 22 types across basic (crossfade, fade), directional (slide, wipe), cinematic (zoom, blur, glitch), and dynamic (flash, spin, pixelate) categories.
22 types, 4 categories, batch-rendered MP4 preview clips at 320x180
For projects needing post-production polish, Rendomat exports a full AE project. A JSON manifest describes every layer, keyframe, and easing curve. An ExtendScript importer reconstructs the composition in After Effects with accurate timing, blend modes, and bezier easing. Rendomat handles the 90% case; AE handles the last 10%.
JSON manifest generation, ExtendScript (JSX) importer, keyframe reconstruction
Claude API takes raw documents (markdown, Word, pasted text) and structures them into typed scene data: deciding scene types, splitting content, generating chart data, and writing copy. Every decision is stored as editable fields. Users see results, then override anything. The model accelerates without removing control.
Document parsing, scene-type selection, chart data generation, copy refinement
The Express server, Next.js frontend, Remotion renderer, and FFmpeg pipeline all run inside a single Electron process. The main process starts the Express server programmatically and opens a BrowserWindow pointed at the static Next.js build. All file paths use process.cwd() so they resolve correctly whether running as a Node.js server or an Electron app. SQLite, file uploads, and scene cache live in the app's user data directory. No architecture changes were needed. The existing localhost REST pattern became an internal IPC channel.
Electron main process as Express host, static Next.js in renderer, bundled FFmpeg + Chrome
Local rendering ships today in the desktop app, but it ties up the user's machine for minutes. The cloud render pipeline, currently in progress, offloads the entire job to @remotion/lambda. A FullVideoComposition bundles all scenes and transitions into a single composition, Lambda fans out rendering across parallel workers, and the final video lands in S3. The user sees real-time progress via SSE, then gets a download link. Credits gate access: 30 free on signup, more via Stripe. The same SSE infrastructure serves both local and cloud progress.
FullVideoComposition pattern, Lambda fan-out, S3 delivery, SSE progress reuse
Neon Auth (better-auth sessions) handles Google + GitHub OAuth plus email sign-in. On first sign-in, the API layer upserts the user record and grants signup credits. The session token propagates identity from the Next.js frontend to the API routes. Stripe Checkout Sessions handle payment on the server side; that integration is built and now being wired into the web app. The webhook endpoint is registered before express.json() middleware so raw body parsing preserves the signature. Credits are atomic: adjustCredits() wraps the balance update and transaction log in a SQLite transaction. Idempotency is enforced by checking stripe_session_id before fulfillment.
Neon Auth sessions → API routes, Stripe webhook with signature verification, atomic credit transactions
Systems Design
Software systems aren't static structures. They're dynamic systems with reinforcing loops and balancing constraints. Understanding these patterns explains why the architecture looks the way it does better than any component diagram.
Per-scene SHA256 caching creates a virtuous cycle. Fast renders (under 5 seconds for single-scene edits) encourage more iterations. More iterations produce better output. Better output builds trust in the tool, which drives deeper usage of advanced features. The caching system isn't just a performance optimization. It's the engine of user engagement.
Each new scene type expands what Claude can generate from a document. When I added the equation scene, the system could suddenly handle academic papers. When I added the spotlights scene, product walkthrough documents became viable input. The content-structuring model and the scene library co-evolve: broader scenes train better structuring, and better structuring justifies more scene types.
Video rendering is CPU-intensive and blocks the Node.js event loop. This constraint forced three architectural decisions: (1) per-scene caching to minimize redundant renders, (2) separate render worker processes, and (3) the transition system rendering independently from scenes. A downstream constraint became an upstream organizing principle: the entire pipeline is shaped by the need to minimize render operations.
Supporting 16:9, 1:1, and 9:16 from the same content forced a clean separation between scene data (what to show) and composition (how to lay it out). This constraint propagated through the entire system: the database stores content-only data, the Remotion components handle layout per aspect ratio, and themes are resolution-independent. Platform fragmentation as an input produced better architecture as an output.
Design Rationale
Four principles that shaped the interface. The editorial visual style (sharp corners, warm tones, serif headlines) was a deliberate departure from generic SaaS aesthetics. The tool should feel like a creative environment, not an admin panel.
Progressive Disclosure
New users see a text input and a Generate button. The timeline, animation presets, and multi-track audio only appear after content exists. This prevents the blank-canvas problem where complex tools overwhelm before you start.
Direct Manipulation
Scenes are physical objects on the timeline: grab, drag, resize. No modal dialogs for reordering. No numeric inputs for duration. Interactions map directly to the data model.
Claude as Co-pilot
Claude generates a complete first draft from the document. Every decision is editable: scene type, content, animation, timing. The system accelerates without removing creative authority.
Familiar Mental Models
The multi-track timeline borrows directly from Premiere and DaVinci. Scenes, transitions, audio, and video live on separate tracks. Anyone with editing experience transfers their knowledge immediately.
Who it's for
Three archetypes spanning low-tech researchers to professional editors. The system needed to serve all three without compromising depth for simplicity or vice versa.
Marketing Manager, B2B SaaS Startup
Runs a lean team of three. Needs video for launches and LinkedIn, with no production budget.
Goals
Pain Points
Freelance Video Editor, Independent
Handles 8-12 client projects per month. Uses AE for polish but needs faster rough cuts.
Goals
Pain Points
Research Communicator, University Hospital
Translates medical research into accessible content. Zero tolerance for fussy interfaces.
Goals
Pain Points
The Journey
Four phases from document to rendered video. The editing loop is intentionally circular: preview renders feed back into the timeline for iteration before committing to a full export.
Cloud rendering is in progress. Once wired up, authenticated users will offload rendering to AWS Lambda: the frontend sends a session token, a credit is deducted, Lambda fans out the work, and the completed video lands in S3.
Infrastructure
Three infrastructure systems that turn a local tool into a deployed product: Neon Auth authentication (live), credits-based Stripe billing (built server-side, now being wired into the web app), and serverless video rendering on AWS Lambda (in progress).
Neon Auth (better-auth sessions) + Next.js API routes
Neon Auth (better-auth sessions) handles Google and GitHub OAuth plus email sign-in. The session token is sent as a Bearer token to the API layer. The getAuthUser helper validates the session against Neon Auth, upserts the user record via Prisma on first sign-in, and resolves the user for all protected endpoints.
The better-auth session token issued by Neon Auth travels as a Bearer token from the browser to the API routes. One credential flows through the entire stack.
getAuthUser() upserts the user record via Prisma the first time a session is seen. This initializes 30 signup credits with an audit transaction.
User records, credit balances, and transaction history live in the database. The session carries identity; the database carries state.
Credits-based payments with atomic fulfillment
Cloud renders will cost credits. Users start with 30 free credits on signup and will purchase more through Stripe Checkout, which is built server-side and now being wired into the web app. The webhook endpoint is registered before express.json() middleware so the raw body is available for signature verification. Fulfillment is idempotent. The handler checks stripe_session_id before adjusting credits, preventing double-credit on webhook retries.
Credit Packages
Transaction Types
+30 credits on first sign-in
Stripe Checkout fulfillment via webhook
-1 credit per cloud render invocation
The /api/stripe/webhook route uses express.raw() and Stripe's constructEvent to verify the webhook signature before processing. Registered before the JSON body parser to preserve the raw body.
adjustCredits() wraps the balance UPDATE and transaction INSERT in a single SQLite transaction. If either fails, both roll back. No partial state.
@remotion/lambda + S3 + SSE progress (in progress)
Local rendering ships in the desktop app and ties up the user's machine for 3-5 minutes. The cloud pipeline, currently in progress, offloads the entire job to @remotion/lambda. A FullVideoComposition bundles all scenes and transitions into a single Remotion composition, Lambda fans out rendering across parallel workers (40 frames per worker), and the final video lands in S3. The user will see real-time progress via Server-Sent Events, then get a download link.
Lambda Configuration
Render Lifecycle
Credit deducted, props built from DB, Lambda invoked
Parallel workers render frames, SSE polls every 2s
S3 URL returned, video record updated, download ready
Credit refund, error logged, user notified via SSE
Unlike local rendering which caches individual scenes, Lambda renders a single composition containing all scenes and transitions. The tradeoff: no incremental caching, but massive parallelism across workers.
The same Server-Sent Events infrastructure serves both local and cloud progress. The frontend doesn't care where the video is rendering. It just subscribes to the progress stream.
The Lambda function name and composition ID are derived from the video's aspect ratio. A 16:9 video renders as FullVideo-16x9, a 9:16 as FullVideo-9x16. Same code, different compositions.
Reflections
I built the data model and rendering pipeline first, then the visual timeline. In retrospect, the timeline is what makes the tool usable for non-technical people. Building it earlier would have surfaced UX issues sooner and driven better API design.
Early prototypes re-rendered the entire video on every change. Users abandoned editing after 2-3 iterations. Per-scene SHA256 caching reduced feedback time from 45 seconds to under 5. That single optimization made the product viable.
Rendering blocks the Node.js event loop. I eventually moved it to a separate process, but should have done that from day one. Early architectural decisions about process boundaries save significant refactoring later.
I initially showed every generation decision in real time (scene type selection, content splitting, animation matching). It created anxiety, not trust. The final design shows the result, then lets users inspect and override each decision at their own pace.
The batch pipeline that renders 320x180 MP4 preview clips for transitions made the UI dramatically better. Seeing actual rendered motion instead of text labels changed how users selected transitions. The same approach now extends to theme and text layout previews.
Try the editor yourself. Paste content, edit on the timeline, and export.
Open the editor