Skip to main content

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

Fetch Project Files

Load all files from Convex database
2

Build File Tree

Construct full paths by traversing parent hierarchy
3

Write to Container

Sync files to WebContainer’s virtual filesystem
4

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.

Performance

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

1

Create package.json

Add a file with dev script
2

Open Terminal

Click terminal panel in IDE
3

Install Dependencies

Run npm install
4

Start Server

Run npm run dev
5

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\nFailed 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

  1. Check package.json has correct scripts
  2. Ensure dependencies are installed (npm install)
  3. Verify port isn’t already in use
  4. Check browser console for errors

Preview Not Loading

Make sure your dev server is actually running. Look for “Server ready” message in terminal.

Slow Performance

  • 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

Build docs developers (and LLMs) love