Skip to main content

Overview

Deploying Stagehand to production requires careful consideration of infrastructure, error handling, monitoring, and cost management. This guide covers deployment strategies and best practices.

Environment Configuration

Use Browserbase for Production

const stagehand = new Stagehand({
  env: "BROWSERBASE",
  apiKey: process.env.BROWSERBASE_API_KEY,
  projectId: process.env.BROWSERBASE_PROJECT_ID,
  browserbaseSessionCreateParams: {
    browserSettings: {
      blockAds: true,
      solveCaptchas: true,
    },
    proxies: true,
  },
});
Why Browserbase:
  • Managed browser infrastructure
  • Automatic scaling
  • Built-in proxy support
  • CAPTCHA solving
  • Session recording for debugging
  • No browser maintenance overhead

Local Environment (Development)

const stagehand = new Stagehand({
  env: "LOCAL",
  localBrowserLaunchOptions: {
    headless: true,
  },
});
Use local for:
  • Development and testing
  • Cost-sensitive workflows
  • Self-hosted infrastructure

Environment Variables

Required Variables

# .env.production
BROWERBASE_API_KEY=your_api_key
BROWERBASE_PROJECT_ID=your_project_id

# LLM Provider (choose one)
OPENAI_API_KEY=your_openai_key
ANTHROPIC_API_KEY=your_anthropic_key

# Optional
STAGEHAND_CACHE_DIR=/app/cache
STAGEHAND_VERBOSE=0
STAGEHAND_TIMEOUT_MS=30000

Load Configuration

import dotenv from "dotenv";

// Load environment-specific config
if (process.env.NODE_ENV === "production") {
  dotenv.config({ path: ".env.production" });
} else {
  dotenv.config({ path: ".env.development" });
}

const stagehand = new Stagehand({
  env: process.env.STAGEHAND_ENV as "LOCAL" | "BROWSERBASE",
  apiKey: process.env.BROWSERBASE_API_KEY,
  projectId: process.env.BROWSERBASE_PROJECT_ID,
  model: process.env.STAGEHAND_MODEL,
  verbose: parseInt(process.env.STAGEHAND_VERBOSE || "0") as 0 | 1 | 2,
  cacheDir: process.env.STAGEHAND_CACHE_DIR,
});

Caching Strategy

Deploy with Pre-Warmed Cache

// scripts/warmCache.ts
import { Stagehand } from "@browserbasehq/stagehand";

async function warmCache() {
  const stagehand = new Stagehand({
    env: "LOCAL",
    cacheDir: "./production-cache",
  });
  await stagehand.init();

  const page = stagehand.context.pages()[0];

  // Run all production workflows once
  await page.goto("https://your-app.com/login");
  await stagehand.act("click the login button");
  await stagehand.act("type '[email protected]' in email field");

  await page.goto("https://your-app.com/products");
  await stagehand.extract("get all product prices");

  // Add more workflows...

  await stagehand.close();
  console.log("Cache warmed successfully");
}

warmCache();
Deployment:
# Warm cache locally
npm run warm-cache

# Include cache in Docker image or deploy

Cache Persistence

# Dockerfile
FROM node:18

WORKDIR /app

COPY package*.json ./
RUN npm ci --production

COPY . .

# Include pre-warmed cache
COPY ./production-cache /app/cache

ENV STAGEHAND_CACHE_DIR=/app/cache

CMD ["node", "dist/index.js"]

Error Handling

Comprehensive Error Handling

import {
  ActTimeoutError,
  StagehandError,
  CdpConnectionClosedError,
} from "@browserbasehq/stagehand";
import * as Sentry from "@sentry/node";

async function executeWorkflow(instruction: string) {
  const stagehand = new Stagehand({
    env: "BROWSERBASE",
    apiKey: process.env.BROWSERBASE_API_KEY,
    projectId: process.env.BROWSERBASE_PROJECT_ID,
  });

  try {
    await stagehand.init();
    const result = await stagehand.act(instruction);
    return result;
  } catch (error) {
    // Log to monitoring service
    Sentry.captureException(error, {
      tags: {
        operation: "stagehand-workflow",
        instruction,
      },
    });

    // Handle specific errors
    if (error instanceof ActTimeoutError) {
      throw new Error("Operation timed out");
    } else if (error instanceof CdpConnectionClosedError) {
      throw new Error("Browser connection lost");
    } else if (error instanceof StagehandError) {
      throw new Error(`Stagehand error: ${error.message}`);
    }

    throw error;
  } finally {
    await stagehand.close();
  }
}

Retry Logic

async function executeWithRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3,
  delayMs = 1000
): Promise<T> {
  let lastError;

  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;
      console.log(`Attempt ${i + 1}/${maxRetries} failed, retrying...`);

      if (i < maxRetries - 1) {
        await new Promise((resolve) => setTimeout(resolve, delayMs * (i + 1)));
      }
    }
  }

  throw lastError;
}

// Usage
const result = await executeWithRetry(async () => {
  const stagehand = new Stagehand({ env: "BROWSERBASE" });
  await stagehand.init();
  const result = await stagehand.act("click button");
  await stagehand.close();
  return result;
});

Monitoring and Logging

Structured Logging

import winston from "winston";

const logger = winston.createLogger({
  level: "info",
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: "error.log", level: "error" }),
    new winston.transports.File({ filename: "combined.log" }),
  ],
});

const stagehand = new Stagehand({
  env: "BROWSERBASE",
  verbose: 1,
  logger: (line) => {
    logger.log({
      level: line.level === 0 ? "error" : "info",
      category: line.category,
      message: line.message,
      ...line.auxiliary,
    });
  },
});

Metrics Collection

import { Counter, Histogram } from "prom-client";

const actCounter = new Counter({
  name: "stagehand_act_total",
  help: "Total number of act() calls",
  labelNames: ["status"],
});

const actDuration = new Histogram({
  name: "stagehand_act_duration_seconds",
  help: "Duration of act() calls",
  buckets: [0.1, 0.5, 1, 2, 5, 10],
});

async function monitoredAct(stagehand: Stagehand, instruction: string) {
  const start = Date.now();

  try {
    const result = await stagehand.act(instruction);
    actCounter.inc({ status: "success" });
    actDuration.observe((Date.now() - start) / 1000);
    return result;
  } catch (error) {
    actCounter.inc({ status: "error" });
    throw error;
  }
}

Browserbase Session Tracking

const stagehand = new Stagehand({
  env: "BROWSERBASE",
  apiKey: process.env.BROWSERBASE_API_KEY,
  projectId: process.env.BROWSERBASE_PROJECT_ID,
});
await stagehand.init();

// Log session info for debugging
console.log("Session ID:", stagehand.browserbaseSessionID);
console.log("Session URL:", stagehand.browserbaseSessionURL);
console.log("Debug URL:", stagehand.browserbaseDebugURL);

// Store session ID for later replay
const sessionId = stagehand.browserbaseSessionID;

// Later, reconnect to the same session
const reconnected = new Stagehand({
  env: "BROWSERBASE",
  browserbaseSessionID: sessionId,
});

Scaling Strategies

Worker Queue Pattern

import { Queue, Worker } from "bullmq";

const taskQueue = new Queue("stagehand-tasks", {
  connection: {
    host: "redis",
    port: 6379,
  },
});

// Add tasks to queue
await taskQueue.add("scrape", {
  url: "https://example.com",
  instruction: "extract product data",
});

// Worker processes tasks
const worker = new Worker(
  "stagehand-tasks",
  async (job) => {
    const stagehand = new Stagehand({ env: "BROWSERBASE" });
    await stagehand.init();

    try {
      const page = stagehand.context.pages()[0];
      await page.goto(job.data.url);
      const result = await stagehand.extract(job.data.instruction);
      return result;
    } finally {
      await stagehand.close();
    }
  },
  {
    connection: {
      host: "redis",
      port: 6379,
    },
    concurrency: 5, // Process 5 jobs in parallel
  }
);

Serverless Deployment

// AWS Lambda handler
import { Stagehand } from "@browserbasehq/stagehand";

export const handler = async (event: any) => {
  const stagehand = new Stagehand({
    env: "BROWSERBASE", // Always use Browserbase for serverless
    apiKey: process.env.BROWSERBASE_API_KEY,
    projectId: process.env.BROWSERBASE_PROJECT_ID,
    cacheDir: "/tmp/stagehand-cache", // Lambda /tmp storage
  });

  try {
    await stagehand.init();
    const page = stagehand.context.pages()[0];
    await page.goto(event.url);
    const result = await stagehand.extract(event.instruction);

    return {
      statusCode: 200,
      body: JSON.stringify(result.extraction),
    };
  } catch (error) {
    console.error("Error:", error);
    return {
      statusCode: 500,
      body: JSON.stringify({ error: error.message }),
    };
  } finally {
    await stagehand.close();
  }
};
serverless.yml:
service: stagehand-service

provider:
  name: aws
  runtime: nodejs18.x
  timeout: 300 # 5 minutes
  memorySize: 2048
  environment:
    BROWSERBASE_API_KEY: ${env:BROWSERBASE_API_KEY}
    BROWSERBASE_PROJECT_ID: ${env:BROWSERBASE_PROJECT_ID}

functions:
  scrape:
    handler: handler.handler
    events:
      - http:
          path: scrape
          method: post

Container Deployment

# Dockerfile
FROM node:18-slim

WORKDIR /app

# Install dependencies
COPY package*.json ./
RUN npm ci --production

# Copy application
COPY . .

# Copy pre-warmed cache
COPY ./production-cache /app/cache

# Environment
ENV NODE_ENV=production
ENV STAGEHAND_CACHE_DIR=/app/cache

# Health check
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
  CMD node healthcheck.js

CMD ["node", "dist/index.js"]
docker-compose.yml:
version: "3.8"

services:
  stagehand-worker:
    build: .
    environment:
      - BROWSERBASE_API_KEY=${BROWSERBASE_API_KEY}
      - BROWSERBASE_PROJECT_ID=${BROWSERBASE_PROJECT_ID}
      - REDIS_URL=redis://redis:6379
    depends_on:
      - redis
    deploy:
      replicas: 3

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

Cost Optimization

Session Reuse

const stagehand = new Stagehand({
  env: "BROWSERBASE",
  keepAlive: true, // Keep session alive
});
await stagehand.init();

// Reuse for multiple operations
for (const task of tasks) {
  await executeTask(stagehand, task);
}

// Close when done
await stagehand.close();

Batch Processing

// Process multiple tasks in one session
async function batchProcess(tasks: Task[]) {
  const stagehand = new Stagehand({ env: "BROWSERBASE" });
  await stagehand.init();

  const results = [];

  for (const task of tasks) {
    const page = stagehand.context.pages()[0];
    await page.goto(task.url);
    const result = await stagehand.extract(task.instruction);
    results.push(result);
  }

  await stagehand.close();
  return results;
}

Security

Secure Credentials

// Use environment variables, never hardcode
const stagehand = new Stagehand({
  env: "BROWSERBASE",
  apiKey: process.env.BROWSERBASE_API_KEY, // ✅
  projectId: process.env.BROWSERBASE_PROJECT_ID, // ✅
  // apiKey: "bb_live_...", // ❌ Never hardcode
});

Secrets Management

import { SecretsManager } from "@aws-sdk/client-secrets-manager";

const secretsManager = new SecretsManager({ region: "us-east-1" });

const secret = await secretsManager.getSecretValue({
  SecretId: "stagehand/production",
});

const credentials = JSON.parse(secret.SecretString!);

const stagehand = new Stagehand({
  env: "BROWSERBASE",
  apiKey: credentials.browserbase_api_key,
  projectId: credentials.browserbase_project_id,
});

Health Checks

// healthcheck.js
import { Stagehand } from "@browserbasehq/stagehand";

async function healthCheck() {
  try {
    const stagehand = new Stagehand({
      env: "BROWSERBASE",
      apiKey: process.env.BROWSERBASE_API_KEY,
      projectId: process.env.BROWSERBASE_PROJECT_ID,
    });

    await stagehand.init();
    const page = stagehand.context.pages()[0];
    await page.goto("https://example.com");
    await stagehand.close();

    console.log("Health check passed");
    process.exit(0);
  } catch (error) {
    console.error("Health check failed:", error);
    process.exit(1);
  }
}

healthCheck();

Testing in Production

Canary Deployments

const useNewVersion = Math.random() < 0.1; // 10% traffic

const stagehand = new Stagehand({
  env: "BROWSERBASE",
  model: useNewVersion ? "openai/gpt-4.1" : "openai/gpt-4.1-mini",
});

Feature Flags

import LaunchDarkly from "launchdarkly-node-server-sdk";

const ldClient = LaunchDarkly.init(process.env.LAUNCH_DARKLY_KEY!);
await ldClient.waitForInitialization();

const useCaching = await ldClient.variation(
  "stagehand-caching-enabled",
  { key: "user-id" },
  false
);

const stagehand = new Stagehand({
  env: "BROWSERBASE",
  cacheDir: useCaching ? "./cache" : undefined,
});

Deployment Checklist

1

Environment Configuration

Set up Browserbase credentials and environment variables
2

Enable Caching

Pre-warm cache and deploy with cached workflows
3

Error Handling

Implement comprehensive error handling and retries
4

Monitoring

Set up logging, metrics, and alerting
5

Scaling Strategy

Choose between serverless, containers, or workers
6

Cost Optimization

Enable session reuse and batch processing
7

Security

Use secrets management for credentials
8

Health Checks

Implement health checks for monitoring
9

Testing

Test in production with canaries or feature flags

Build docs developers (and LLMs) love