Prototype Preview Viewer
Self-hosted platform for uploading, managing, and live-previewing React component prototypes in the browser — with ZIP upload, in-browser JSX transpilation, and sandboxed rendering.
Built a self-hosted platform for uploading React projects as ZIP files and live-previewing them in the browser — no build step required. Features a custom in-browser module system with Babel transpilation, sandboxed iframe rendering, and a dashboard for managing multiple prototypes. Supports multi-file projects with nested imports, CSS injection, and real-time data simulation.
The Problem
Sharing interactive React prototypes with stakeholders is harder than it should be. You either send a Figma link (which isn't real code), deploy a full app (heavy for a quick prototype), or share a CodeSandbox (requires internet, has limitations). I wanted a way to zip up a React project, upload it somewhere, and have it run instantly — no npm install, no build step, no deployment pipeline. Just upload and preview.
The target workflow: zip your React components, drag-and-drop into a dashboard, click "View," and see your prototype running live in the browser. Under 30 seconds from files on your machine to an interactive preview.
How It Works
The system has three layers:
1. Upload Pipeline — The dashboard accepts a ZIP file (up to 10MB) with a project name, description, and entry point path. The server extracts the ZIP with AdmZip, auto-detects and strips root folders, classifies files by type (component/style/other), and persists everything to the filesystem with a metadata.json manifest.
2. RESTful API — Standard CRUD routes handle the lifecycle: list all projects, get a single project with all file contents, upload a new project, delete a project. Projects are stored as actual files under public/projects/, making them inspectable and debuggable without a database.
3. The Runtime — This is where it gets interesting.
The Browser Runtime
Custom In-Browser Module System
When you click "View," the system generates a complete self-contained HTML document as a string, injects it into an iframe via srcDoc, and bootstraps a custom module resolver inside. All project files are embedded as a JSON map. A hand-rolled requireModule() function handles recursive dependency resolution — transforming ES module import statements into require() calls via regex, then transpiling JSX with Babel Standalone in real-time.
The module resolver supports relative imports (./, ../) with proper directory traversal, auto-extension resolution (.tsx, .jsx, .ts, .js), case-insensitive fallback matching, and module caching to avoid re-processing. It's essentially a miniature bundler running entirely in the browser.
Sandboxed Iframe Rendering
Each prototype runs in its own iframe with sandbox="allow-scripts allow-same-origin". This prevents prototypes from interfering with the host application's CSS or JavaScript — critical when rendering untrusted uploaded code. The iframe loads React 18 from unpkg CDN, keeping the runtime self-contained.
Multi-File Project Support
This isn't limited to single-file demos. Upload a project with nested folders, multiple components, shared utilities, and CSS files — the module system resolves cross-file imports and the CSS injector inserts stylesheets into the iframe head. The included DevOps dashboard example has 5 navigable views, shared components, and live streaming data simulation.
Architecture Trade-offs
Every major decision was a deliberate trade-off:
Babel Standalone over server-side bundling — Transpilation happens client-side, eliminating the need for a build pipeline on the server. This trades initial load performance for architectural simplicity. For prototype previewing (not production), the trade-off is worth it.
Regex-based import transformation over AST parsing — Rather than using a full AST parser like Babel's transform plugins, imports are rewritten via regex patterns. This handles the common cases (default imports, named imports, React-specific imports) while keeping the code compact. A full parser would be more robust but dramatically more complex.
Filesystem over database — Projects are stored as actual files, not database rows. This makes them inspectable with standard tools, eliminates database dependencies, and keeps the architecture dead simple. The metadata.json file provides the index.
CDN React 18 in iframe vs. React 19 on host — The platform runs React 19 (via Next.js 16), but prototypes execute against React 18 from unpkg. This ensures compatibility with Babel Standalone's transpilation pipeline while letting the host app use the latest React features.
Example Prototypes
Three example projects ship with the platform, demonstrating progressive complexity:
- Simple Counter — Single-file React app with basic hooks. Validates the core upload-and-preview pipeline.
- Financial Trading Dashboard — Multi-component app with AccountBalance, MarketData, PriceChart, and TradeTicket components. Simulates real-time market data updates with
setInterval. - DevOps Monitoring Dashboard — The stress test: 5 navigable views (Dashboard, Containers, Deployments, Logs, Metrics), shared Header and Navigation components, live streaming data, and complex state management — all running inside the sandboxed iframe.
What I Learned
Modern web development assumes Webpack/Vite/Turbopack for everything. But for isolated previews of small-to-medium React projects, Babel Standalone in an iframe is surprisingly capable. The entire "bundler" is under 200 lines of JavaScript. It doesn't scale to production apps, but for the prototype preview use case, it's perfect.
The regex approach handles 90% of import patterns elegantly. But edge cases — namespace imports, type-only imports, dynamic imports, re-exports — break the pattern. For this project, documenting the limitations was the right call. For a production tool, an AST-based approach would be necessary.
Running user-uploaded code is inherently risky. The iframe sandbox provides meaningful isolation with minimal complexity — no Docker containers, no WebWorker message passing, no WASM sandboxes. For a self-hosted tool where you trust the uploaders, sandbox="allow-scripts allow-same-origin" is the pragmatic choice.