Overview
Polaris IDE leverages WebContainer API by StackBlitz to provide a complete Node.js runtime in the browser - no backend servers required.
In-Browser Node.js Full Node.js environment running in your browser
Terminal Access Run npm commands, scripts, and shell utilities
Live Preview See your web app running instantly with hot reload
What is WebContainer?
WebContainer is a browser-based runtime that executes Node.js applications entirely client-side using WebAssembly:
WebContainer runs Node.js in your browser tab - no Docker containers, VMs, or remote servers needed!
import { WebContainer } from "@webcontainer/api" ;
// Boot WebContainer instance
const container = await WebContainer . boot ();
// It's a full Node.js environment!
await container . spawn ( "npm" , [ "install" ]);
await container . spawn ( "npm" , [ "run" , "dev" ]);
Architecture
WebContainer Provider
Polaris initializes WebContainer at the project level:
// From preview/context.tsx
export function WebContainerProvider ({ children , projectId }) {
const [ container , setContainer ] = useState < WebContainer | null >( null );
const [ isBooting , setIsBooting ] = useState ( true );
const files = useQuery ( api . files . getFiles , { projectId });
useEffect (() => {
getWebContainer ()
. then (( wc ) => {
setContainer ( wc );
setIsBooting ( false );
// Listen for dev server
wc . on ( "server-ready" , ( _port , url ) => {
setServerUrl ( url );
});
});
}, []);
return (
< WebContainerContext . Provider value = {{ container , serverUrl }} >
{ children }
</ WebContainerContext . Provider >
);
}
Singleton Pattern
WebContainer is initialized once and reused:
// From lib/webcontainer.ts
let containerInstance : WebContainer | null = null ;
export async function getWebContainer () : Promise < WebContainer > {
if ( ! containerInstance ) {
containerInstance = await WebContainer . boot ();
}
return containerInstance ;
}
export function teardownWebContainer () {
containerInstance = null ;
}
Only one WebContainer instance can exist per browser tab. Attempting to boot multiple instances will fail.
File Synchronization
Polaris automatically syncs your Convex files to the WebContainer filesystem:
Fetch Project Files
Load all files from Convex database
Build File Tree
Construct full paths by traversing parent hierarchy
Write to Container
Sync files to WebContainer’s virtual filesystem
Watch for Changes
Update container when files change in database
// From preview/context.tsx
useEffect (() => {
if ( ! container || ! files ) return ;
const syncFiles = async () => {
const fileEntries : FileEntry [] = [];
// Build full paths
const buildPath = ( fileId : string ) : string => {
const pathSegments : string [] = [];
let currentId = fileId ;
while ( currentId ) {
const file = fileMap . get ( currentId );
if ( ! file ) break ;
pathSegments . unshift ( file . name );
currentId = file . parentId ;
}
return pathSegments . join ( "/" );
};
// Sync to container
await syncFilesToContainer ( container , fileEntries );
};
syncFiles ();
}, [ container , files ]);
// From preview/lib/sync.ts
export async function syncFilesToContainer (
container : WebContainer ,
files : FileEntry []
) {
for ( const file of files ) {
if ( file . type === "folder" ) {
await container . fs . mkdir ( file . path , { recursive: true });
} else {
// Create parent directories
const dirPath = file . path . split ( "/" ). slice ( 0 , - 1 ). join ( "/" );
if ( dirPath ) {
await container . fs . mkdir ( dirPath , { recursive: true });
}
// Write file content
await container . fs . writeFile ( file . path , file . content );
}
}
}
Terminal Integration
Polaris provides a full terminal powered by xterm.js :
Terminal Features
Interactive Shell Full bash-like shell (jsh) with command history
npm Commands Install packages and run scripts
Process Management Start, stop, and manage processes
Custom Theme Catppuccin-inspired color scheme
Terminal Implementation
// From preview/components/terminal.tsx
import { Terminal as XTerm } from "@xterm/xterm" ;
import { FitAddon } from "@xterm/addon-fit" ;
export function Terminal () {
const { container } = useWebContainer ();
useEffect (() => {
const term = new XTerm ({
theme: {
background: "#1e1e2e" ,
foreground: "#cdd6f4" ,
cursor: "#f5e0dc" ,
// ... Catppuccin colors
},
fontSize: 13 ,
fontFamily: 'Menlo, Monaco, "Courier New", monospace' ,
cursorBlink: true ,
});
const fitAddon = new FitAddon ();
term . loadAddon ( fitAddon );
term . open ( terminalRef . current );
fitAddon . fit ();
// Spawn shell process
const shellProcess = await container . spawn ( "jsh" , {
terminal: {
cols: term . cols ,
rows: term . rows ,
},
});
// Pipe output to terminal
shellProcess . output . pipeTo (
new WritableStream ({
write ( data ) {
term . write ( data );
},
})
);
// Send user input to shell
const input = shellProcess . input . getWriter ();
term . onData (( data ) => {
input . write ( data );
});
}, [ container ]);
}
Running Commands
You can run any Node.js command:
# Install dependencies
npm install
# Run dev server
npm run dev
# Execute scripts
node script.js
# Use package binaries
npx create-react-app my-app
Commands run with full Node.js compatibility - most npm packages work out of the box!
Live Preview
Server Detection
WebContainer automatically detects when your app starts a server:
container . on ( "server-ready" , ( port , url ) => {
console . log ( `Server running at ${ url } ` );
setServerUrl ( url );
});
The preview URL is typically https://[random-id].local.webcontainer.io and works just like a real server!
Preview Frame Component
Display your running app in an iframe:
// From preview/components/preview-frame.tsx
export function PreviewFrame () {
const { serverUrl } = useWebContainer ();
if ( ! serverUrl ) {
return < div > Start a dev server to see preview </ div > ;
}
return (
< iframe
src = { serverUrl }
className = "w-full h-full"
sandbox = "allow-scripts allow-same-origin allow-forms"
/>
);
}
Hot Reload Support
Most frameworks’ hot reload works seamlessly:
Vite - HMR updates instantly
Next.js - Fast Refresh works
Create React App - Hot reloading enabled
Parcel - Auto-reloads on changes
Process Management
Manage running processes in WebContainer:
const [ currentProcess , setCurrentProcess ] = useState < WebContainerProcess | null >( null );
// Start process
const process = await container . spawn ( "npm" , [ "run" , "dev" ]);
setCurrentProcess ( process );
// Kill process
if ( currentProcess ) {
currentProcess . kill ();
setCurrentProcess ( null );
}
// Listen for exit
process . exit . then (( code ) => {
console . log ( `Process exited with code ${ code } ` );
});
Supported Features
Node.js APIs fs, path, http, https, crypto, process, etc.
npm Ecosystem Install and use most npm packages
Build Tools Vite, webpack, Rollup, esbuild
Frameworks React, Vue, Svelte, Next.js, Remix
Limitations
WebContainer cannot:
Run native binaries or Python/Ruby/Go code
Access your local filesystem directly
Make unrestricted network requests (CORS applies)
Use Node.js features requiring kernel access
Binary Files
Binary files (images, fonts) are skipped during sync:
// Skip binary files (they have storageId)
if ( file . storageId ) continue ;
fileEntries . push ({
path ,
content: file . content || "" ,
type: file . type ,
});
For images and assets, consider using CDN URLs or base64-encoded data URLs in your code.
Boot Time
WebContainer initialization:
First boot : 2-5 seconds (downloading WASM runtime)
Subsequent boots : Less than 1 second (cached)
Execution Speed
WebContainer runs at near-native speed thanks to WebAssembly. Most operations are only 10-20% slower than native Node.js.
Memory Usage
WebContainer uses browser memory:
Base runtime : ~50MB
With dependencies : 100-500MB (depends on project size)
Browser limit : Usually 2-4GB per tab
Common Workflows
Starting a Dev Server
Create package.json
Add a file with dev script
Open Terminal
Click terminal panel in IDE
Install Dependencies
Run npm install
Start Server
Run npm run dev
View Preview
Preview panel shows your running app
Running Tests
# Install test framework
npm install --save-dev vitest
# Run tests
npm test
# Watch mode
npm test -- --watch
Building for Production
# Build optimized bundle
npm run build
# Preview production build
npm run preview
Debugging
Console Logs
Use console.log() in your code:
console . log ( "Debug:" , data );
// Appears in browser DevTools console
Terminal Output
All process output appears in the integrated terminal:
term . writeln ( "Welcome to Polaris Terminal" );
term . writeln ( "Starting shell... \n " );
Error Handling
try {
const process = await container . spawn ( "npm" , [ "run" , "dev" ]);
} catch ( err ) {
term . writeln ( ` \r\n Failed to start: ${ err } ` );
}
Best Practices
Use .gitignore Exclude node_modules and build folders
Keep Dependencies Minimal Smaller dependency trees load faster
Leverage Dev Servers Use Vite/webpack dev servers for best experience
Monitor Memory Large projects may hit browser memory limits
Troubleshooting
Server Won’t Start
Check package.json has correct scripts
Ensure dependencies are installed (npm install)
Verify port isn’t already in use
Check browser console for errors
Preview Not Loading
Make sure your dev server is actually running. Look for “Server ready” message in terminal.
Reduce number of dependencies
Use lighter frameworks (Vite instead of webpack)
Close other heavy browser tabs
Increase browser memory limit
Next Steps
Code Editor Edit your code with the powerful code editor
AI Assistance Get AI help with coding and debugging