Polaris uses WebContainer API from StackBlitz to execute Node.js code directly in the browser. This enables real-time preview of web applications without requiring a backend server.
Overview
WebContainer creates a full Node.js environment in the browser that can:
Install npm packages
Run build scripts and dev servers
Serve web applications with live preview
Execute shell commands
Hot-reload code changes
WebContainer requires specific browser headers for security. Polaris uses coep: "credentialless" mode for Cross-Origin Embedder Policy compatibility.
Architecture
The WebContainer implementation uses a singleton pattern to ensure only one instance runs at a time:
// Singleton WebContainer instance
let webcontainerInstance : WebContainer | null = null ;
let bootPromise : Promise < WebContainer > | null = null ;
const getWebContainer = async () : Promise < WebContainer > => {
if ( webcontainerInstance ) {
return webcontainerInstance ;
}
if ( ! bootPromise ) {
bootPromise = WebContainer . boot ({ coep: "credentialless" });
}
webcontainerInstance = await bootPromise ;
return webcontainerInstance ;
};
Source: src/features/preview/hooks/use-webcontainer.ts:13-28
Lifecycle Stages
The WebContainer preview goes through several stages:
Booting
Initialize the WebContainer instance and prepare the browser environment.
Mounting
Convert the flat file structure from Convex into a nested FileSystemTree and mount it to the container’s filesystem. const fileTree = buildFileTree ( files );
await container . mount ( fileTree );
Installing
Run the install command (default: npm install) to install dependencies. const installCmd = settings ?. installCommand || "npm install" ;
const [ installBin , ... installArgs ] = installCmd . split ( " " );
const installProcess = await container . spawn ( installBin , installArgs );
Running
Execute the dev command (default: npm run dev) and wait for the server to be ready. container . on ( "server-ready" , ( _port , url ) => {
setPreviewUrl ( url );
setStatus ( "running" );
});
File Tree Conversion
Polaris stores files in a flat structure with parent references. The buildFileTree utility converts this into WebContainer’s nested format:
// Input: Flat file array with parentId references
[
{ _id: "1" , name: "src" , type: "folder" , parentId: undefined },
{ _id: "2" , name: "index.js" , type: "file" , parentId: "1" , content: "..." }
]
// Output: Nested FileSystemTree
{
src : {
directory : {
"index.js" : {
file: { contents: "..." }
}
}
}
}
Source: src/features/preview/utils/file-tree.ts:10-55
Hot Reloading
When files change in the editor, Polaris automatically syncs them to the WebContainer filesystem:
useEffect (() => {
const container = containerRef . current ;
if ( ! container || ! files || status !== "running" ) return ;
const filesMap = new Map ( files . map (( f ) => [ f . _id , f ]));
for ( const file of files ) {
if ( file . type !== "file" || file . storageId || ! file . content ) continue ;
const filePath = getFilePath ( file , filesMap );
container . fs . writeFile ( filePath , file . content );
}
}, [ files , status ]);
Source: src/features/preview/hooks/use-webcontainer.ts:145-157
Binary files (with storageId) are not synced to WebContainer as they’re stored separately in Convex storage.
Process Management
Running Commands
Use container.spawn() to execute commands:
const process = await container . spawn ( "npm" , [ "run" , "build" ]);
// Stream output
process . output . pipeTo (
new WritableStream ({
write ( data ) {
console . log ( data );
},
})
);
// Wait for completion
const exitCode = await process . exit ;
if ( exitCode !== 0 ) {
throw new Error ( `Command failed with code ${ exitCode } ` );
}
Listening for Servers
WebContainer automatically detects when a dev server starts:
container . on ( "server-ready" , ( port , url ) => {
console . log ( `Server running on port ${ port } ` );
console . log ( `Preview URL: ${ url } ` );
});
Restart and Teardown
To restart the WebContainer (useful when configuration changes):
const teardownWebContainer = () => {
if ( webcontainerInstance ) {
webcontainerInstance . teardown ();
webcontainerInstance = null ;
}
bootPromise = null ;
};
Source: src/features/preview/hooks/use-webcontainer.ts:30-36
Custom Commands
Users can configure custom install and dev commands in preview settings:
Install Command
Dev Command
Multiple Commands
{
installCommand : "pnpm install"
}
Browser Compatibility
WebContainer requires:
SharedArrayBuffer support
Cross-Origin Isolation headers
Modern browser (Chrome 84+, Edge 84+, Safari 15.2+)
Polaris automatically handles the required headers using Next.js middleware configuration.
Dependencies
WebContainer integration uses:
@webcontainer/api (v1.6.1) - Core WebContainer runtime
File tree utilities for format conversion
React hooks for lifecycle management
Package.json: package.json:59