AlterLabAlterLab
Guide
$0.004

JavaScript Rendering

Scrape dynamic websites that require JavaScript execution using our headless browser infrastructure.

Cost

JavaScript rendering uses Tier 4 (Browser). A basic JS-rendered scrape costs $0.004 per request.

When to Use JS Rendering

Most websites work fine with standard HTTP requests. Use JavaScript rendering only when needed:

Use JS Rendering For:

  • Single Page Applications (React, Vue, Angular)
  • Content loaded via AJAX/fetch
  • Infinite scroll pages
  • Sites requiring user interaction simulation
  • Pages with anti-bot JavaScript checks

Don't Need JS For:

  • Static HTML pages
  • Server-rendered content
  • APIs returning JSON
  • Most news/blog articles
  • Traditional e-commerce product pages

Auto Mode

Use mode: "auto" (default) and AlterLab will automatically detect if JS rendering is needed and escalate only when necessary.

Basic Usage

Enable JavaScript rendering by setting render_js: true in the advanced options:

curl -X POST https://api.alterlab.io/api/v1/scrape \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/spa-page",
    "advanced": {
      "render_js": true
    }
  }'

Wait Conditions

Control when the page is considered "ready" for scraping. This is crucial for SPAs where content loads asynchronously.

ConditionDescriptionBest For
domcontentloadedDOM is ready, external resources may still loadFast pages, minimal JS
loadPage and all resources fully loadedImage-heavy pages
networkidleNo network activity for 500ms (default)SPAs, AJAX-heavy pages
# Wait for network to be idle (default, best for SPAs)
response = requests.post(
    "https://api.alterlab.io/api/v1/scrape",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "url": "https://react-app.example.com",
        "advanced": {
            "render_js": True,
            "wait_condition": "networkidle"
        }
    }
)

# Fast mode - don't wait for all resources
response = requests.post(
    "https://api.alterlab.io/api/v1/scrape",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "url": "https://simple-page.example.com",
        "advanced": {
            "render_js": True,
            "wait_condition": "domcontentloaded"
        }
    }
)

Wait for Selector

Wait for a specific element to appear before capturing the page. This is the most reliable way to ensure dynamic content is loaded.

# Wait for product grid to load
response = requests.post(
    "https://api.alterlab.io/api/v1/scrape",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "url": "https://shop.example.com/products",
        "advanced": {
            "render_js": True
        },
        "wait_for": ".product-grid"  # CSS selector
    }
)

# Wait for specific data attribute
response = requests.post(
    "https://api.alterlab.io/api/v1/scrape",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "url": "https://app.example.com/dashboard",
        "advanced": {
            "render_js": True
        },
        "wait_for": "[data-loaded='true']"
    }
)

Timeout

The selector wait has a default timeout of 30 seconds. If the element doesn't appear, the request will return with whatever content is available.

Capturing Screenshots

Capture a full-page screenshot along with the HTML content. Screenshots are returned as base64-encoded PNG.

import base64

response = requests.post(
    "https://api.alterlab.io/api/v1/scrape",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "url": "https://example.com",
        "advanced": {
            "render_js": True,
            "screenshot": True
        }
    }
)

data = response.json()

# Save screenshot to file
if data.get("screenshot"):
    screenshot_bytes = base64.b64decode(data["screenshot"])
    with open("screenshot.png", "wb") as f:
        f.write(screenshot_bytes)

Cost

Screenshots add +$0.0002 to your request.

PDF Generation

Generate a PDF of the rendered page. Useful for archiving or creating printable versions of web pages.

import base64

response = requests.post(
    "https://api.alterlab.io/api/v1/scrape",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "url": "https://example.com/report",
        "advanced": {
            "render_js": True,
            "generate_pdf": True
        }
    }
)

data = response.json()

# Save PDF to file
if data.get("pdf"):
    pdf_bytes = base64.b64decode(data["pdf"])
    with open("page.pdf", "wb") as f:
        f.write(pdf_bytes)

Cost

PDF generation adds +$0.0004 to your request.

Single Page Apps (SPAs)

Modern SPAs built with React, Vue, or Angular require special handling. Here's a reliable pattern:

# Scraping a React application
response = requests.post(
    "https://api.alterlab.io/api/v1/scrape",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "url": "https://react-store.example.com/products/123",
        "advanced": {
            "render_js": True,
            "wait_condition": "networkidle"
        },
        # Wait for the main content container
        "wait_for": "[data-testid='product-details']",
        "timeout": 60  # Allow more time for complex apps
    }
)

# Scraping a Vue application with lazy loading
response = requests.post(
    "https://api.alterlab.io/api/v1/scrape",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "url": "https://vue-app.example.com/dashboard",
        "advanced": {
            "render_js": True,
            "wait_condition": "networkidle"
        },
        "wait_for": ".dashboard-loaded"
    }
)

Tips for SPAs:

  • 1. Find a reliable selector: Look for elements that only appear after data loads
  • 2. Use networkidle: This waits for all AJAX calls to complete
  • 3. Increase timeout: Complex apps may need 60-90 seconds
  • 4. Check for loading states: Wait for spinners/skeletons to disappear

Infinite Scroll Pages

For pages with infinite scroll, you'll typically get the initial viewport content. For full content, consider using pagination or the async API with custom scroll handling.

# Get initial content from infinite scroll page
response = requests.post(
    "https://api.alterlab.io/api/v1/scrape",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "url": "https://social-feed.example.com",
        "advanced": {
            "render_js": True,
            "wait_condition": "networkidle"
        },
        # Wait for initial items to load
        "wait_for": ".feed-item:nth-child(10)"
    }
)

# For more content, look for pagination APIs
# Many "infinite scroll" sites have underlying REST/GraphQL APIs
# that you can call directly without JS rendering

Pro Tip

Many infinite scroll sites have underlying APIs that return JSON. Use browser DevTools to find these APIs, then scrape them directly without JS rendering for better performance and lower costs.

Cost Optimization

JS rendering costs 4x more than basic scraping. Here's how to minimize costs:

1. Use Auto Mode

Set mode: "auto" and let AlterLab detect when JS is needed. We'll try cheaper methods first.

2. Set Cost Controls

Limit how much you're willing to spend per request:

{
  "url": "https://example.com",
  "cost_controls": {
    "max_tier": "3",     // Don't escalate beyond stealth
    "max_cost": 0.001,   // Cap at $0.001 per request
    "prefer_cost": true  // Optimize for lowest cost
  }
}

3. Cache Results

Enable caching for pages that don't change frequently:

{
  "url": "https://example.com",
  "cache": true,
  "cache_ttl": 3600  // Cache for 1 hour
}

Troubleshooting

Content is empty or incomplete

  • Try using wait_for with a specific selector
  • Increase the timeout value
  • Use wait_condition: "networkidle"

Page shows "Please enable JavaScript"

  • Make sure render_js: true is set in advanced options
  • The site may have additional anti-bot measures - try using a higher tier

Request times out

  • Increase timeout (max 300 seconds)
  • Use sync: false for long-running scrapes
  • Check if the selector in wait_for actually exists

Getting blocked or CAPTCHAs

  • AlterLab automatically handles most anti-bot measures
  • For persistent blocks, we'll escalate to CAPTCHA-solving tier automatically
  • Consider using your own proxies with use_own_proxy: true