If you look at how most cloud IDEs and code sandboxes work today, they almost all share the same underlying architecture: you type code in the browser, it gets sent over a WebSocket to a Node.js or Docker container on a remote server, the server compiles it, and sends the result back.
It works, but it introduces a massive bottleneck: Network Latency.
Every keystroke is fighting against ping times. If the server is under load, or your Wi-Fi drops for a second, your flow state is completely ruined.
While building NitroIDE, I wanted to see if I could bypass the server entirely. I wanted to build an execution engine that responds at the exact speed of thought—0ms latency.
Here is exactly how I built a local-first live preview engine entirely in the browser, and how you can do it too.
The Secret Weapon: iframe and srcdoc
When most developers think of iframes, they think of embedding YouTube videos or loading external URLs via the src attribute.
But iframes have a lesser-known, incredibly powerful attribute called srcdoc.
srcdoc allows you to pass raw HTML, CSS, and JavaScript as a string directly into the iframe. The browser treats this string as a completely isolated, independent web document. Because it never makes a network request to fetch a URL, the rendering is instantaneous.
Step 1: The Basic Architecture
To build a live preview, you just need an editor (like Monaco or CodeMirror) and a target iframe. Every time the user types, you grab the code, combine it into a single HTML document, and inject it into the srcdoc.
Here is the simplified engine:
// Grab the code from your editors
const htmlCode = "
Hello World
";const cssCode = "h1 { color: #00e5ff; font-family: sans-serif; }";
const jsCode = "console.log('Running locally!');";
// The compilation function
function compileAndRun(html, css, js) {
const iframe = document.getElementById('livePreview');
// Combine everything into a single document string
const bundle = ;
<!DOCTYPE html>
<html>
<head>
<style>${css}</style>
</head>
<body>
${html}
<script>${js}<\/script>
</body>
</html>
// Inject and execute instantly
iframe.srcdoc = bundle;
}
Step 2: Taming the Console
The biggest issue with client-side execution is that console.log() inside the iframe will just print to the standard browser DevTools. If you are building an IDE, you want those logs to show up in your own custom UI.
To fix this, we have to inject a script before the user's JavaScript that intercepts the native console methods and pipes them to our parent window using postMessage.
const interceptor = `
<br>
// Save the original methods<br>
const ogLog = console.log;<br>
const ogErr = console.error;</p>
<p>// Override and broadcast<br>
console.log = function(...args) {<br>
window.parent.postMessage({ type: 'log', msg: args.join(' ') }, '*');<br>
ogLog.apply(console, args);<br>
};</p>
<p>console.error = function(...args) {<br>
window.parent.postMessage({ type: 'error', msg: args.join(' ') }, '*');<br>
ogErr.apply(console, args);<br>
};<br>
<\/script><br>
`;</p>
<p>In your main application, you just set up an event listener to catch these messages and render them in your UI:</p>
<p>window.addEventListener('message', (event) => {<br>
if (event.data.type === 'log') {<br>
renderToCustomTerminal(event.data.msg);<br>
}<br>
});</p>
<p>Step 3: Security & The Sandbox<br>
Running user-generated JavaScript directly in the browser is dangerous. If you aren't careful, malicious code could access your parent application's cookies, localStorage, or session tokens.<br>
This is where the sandbox attribute saves the day.</p>
<iframe id="livePreview" sandbox="allow-scripts allow-modals"></iframe>
<p>By adding the sandbox attribute, we strip the iframe of its privileges. By explicitly adding allow-scripts, we let the user's code run, but the iframe remains completely isolated from the parent window's context (often called a unique origin). It cannot access your cookies, and it cannot break out of its container.<br>
The Result: Zero Server, Zero Cost<br>
By shifting the execution from a cloud container to the client's local memory, we achieve a few massive wins:</p>
<ol>
<li> 0ms Latency: The code compiles and renders instantly.</li>
<li> 100% Privacy: The code never leaves the local machine.</li>
<li> Zero Infrastructure Cost: You don't have to pay AWS to spin up Docker containers for your users.
If you want to see this architecture pushed to its absolute limit with the Monaco Editor, integrated CDNs, and virtual file systems, you can play around with the live engine right now at nitroide.com or dig into the full source code on GitHub.
How are you handling live previews in your projects? Let me know in the comments!</li>
</ol>
Top comments (0)