Skip to main content
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:
1

Booting

Initialize the WebContainer instance and prepare the browser environment.
2

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);
3

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);
4

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:
{
  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

Build docs developers (and LLMs) love