> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zenrows.com/llms.txt
> Use this file to discover all available pages before exploring further.

# JavaScript Rendering (Headless Browser)

> Enable headless browser rendering in ZenRows to scrape JavaScript-heavy websites and capture dynamically loaded content behind anti-bot systems.

JavaScript Rendering processes web pages through a headless browser - a browser without a graphical interface that can execute JavaScript and render dynamic content. Many modern websites load content dynamically after the initial page load, making this data invisible to standard HTTP requests that only capture the initial HTML.

When you enable JavaScript Rendering, ZenRows simulates a real browser environment, executing all JavaScript code and waiting for dynamic content to load before returning the fully rendered page. This browser simulation also helps bypass sophisticated anti-bot protections that analyze browser behavior, JavaScript execution patterns, and other browser-specific characteristics that Premium Proxy alone cannot address.

## How JavaScript Rendering Works

JavaScript Rendering launches a headless browser instance that navigates to your target URL just like a regular browser would. The browser executes all JavaScript code, processes CSS, loads additional resources, and waits for the page to fully render before extracting the HTML content.

This process captures content that appears after the initial page load, such as:

* AJAX-loaded product listings
* Single-page application (SPA) navigation
* Dynamic pricing information
* User-generated content loaded via JavaScript

Additionally, the browser simulation helps bypass advanced anti-bot measures that detect:

* Missing browser APIs and properties
* Unusual JavaScript execution patterns
* Absence of browser-specific behaviors
* Automated request signatures

## Basic usage

Enable JavaScript Rendering by adding the `js_render=true` parameter to your ZenRows request:

<CodeGroup>
  ```python Python theme={null}
  # pip install requests
  import requests

  url = 'https://httpbin.io/anything'
  apikey = 'YOUR_ZENROWS_API_KEY'
  params = {
      'url': url,
      'apikey': apikey,
  	'js_render': 'true',
  }
  response = requests.get('https://api.zenrows.com/v1/', params=params)
  print(response.text)
  ```

  ```javascript Node.js theme={null}
  // npm install axios
  const axios = require('axios');

  const url = 'https://httpbin.io/anything';
  const apikey = 'YOUR_ZENROWS_API_KEY';
  axios({
  	url: 'https://api.zenrows.com/v1/',
  	method: 'GET',
  	params: {
  		'url': url,
  		'apikey': apikey,
  		'js_render': 'true',
  	},
  })
      .then(response => console.log(response.data))
      .catch(error => console.log(error));
  ```

  ```java Java theme={null}
  import org.apache.hc.client5.http.fluent.Request;

  public class APIRequest {
      public static void main(final String... args) throws Exception {
          String apiUrl = "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fhttpbin.io%2Fanything&js_render=true";
          String response = Request.get(apiUrl)
                  .execute().returnContent().asString();

          System.out.println(response);
      }
  }
  ```

  ```php PHP theme={null}
  <?php
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, 'https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fhttpbin.io%2Fanything&js_render=true');
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  $response = curl_exec($ch);
  echo $response . PHP_EOL;
  curl_close($ch);
  ?>
  ```

  ```go Go theme={null}
  package main

  import (
      "io"
      "log"
      "net/http"
  )

  func main() {
      client := &http.Client{}
      req, err := http.NewRequest("GET", "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fhttpbin.io%2Fanything&js_render=true", nil)
      if err != nil {
          log.Fatalln(err)
      }
      resp, err := client.Do(req)
      if err != nil {
          log.Fatalln(err)
      }
      defer resp.Body.Close()

      body, err := io.ReadAll(resp.Body)
      if err != nil {
          log.Fatalln(err)
      }

      log.Println(string(body))
  }
  ```

  ```ruby Ruby theme={null}
  # gem install faraday
  require 'faraday'

  url = URI.parse('https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fhttpbin.io%2Fanything&js_render=true')
  conn = Faraday.new()
  conn.options.timeout = 180
  res = conn.get(url, nil, nil)
  print(res.body)
  ```

  ```bash cURL theme={null}
  curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fhttpbin.io%2Fanything&js_render=true"
  ```
</CodeGroup>

This example enables JavaScript Rendering for your request. ZenRows processes the page through a headless browser, executing all JavaScript and returning the fully rendered HTML content, rather than just the initial server response.

## When to use JavaScript Rendering

### Content-related needs:

* **Single-page applications (SPAs)** - React, Vue, Angular applications that load content dynamically
* **E-commerce sites** - Product listings, prices, and reviews loaded via JavaScript
* **Search results** - Dynamic search results and pagination
* **Infinite scroll content** - Content that loads as users scroll down
* **AJAX-heavy websites** - Sites that rely heavily on asynchronous data loading
* **Progressive web apps** - Modern web applications with dynamic content updates

### Protection bypass needs:

* **Advanced anti-bot systems** - Sites that analyze browser fingerprints and JavaScript execution
* **Behavioral detection** - Websites that monitor mouse movements, timing patterns, and user interactions
* **Browser API validation** - Sites that check for the presence of browser-specific APIs and properties
* **CloudFlare challenges** - Advanced protection that requires JavaScript execution to pass
* **Captcha systems** - Some captchas that rely on browser behavior analysis

For the highest success rate, especially on heavily protected websites, combine JavaScript Rendering with Premium Proxy. This combination provides both residential IP addresses and realistic browser behavior:

```python Python theme={null}
# Maximum protection: JS Rendering + Premium Proxy
params = {
    'url': 'https://httpbin.io/anything',
    'apikey': 'YOUR_ZENROWS_API_KEY',
    'js_render': 'true',        # Browser simulation
    'premium_proxy': 'true',    # Residential IP
}
response = requests.get('https://api.zenrows.com/v1/', params=params)
print(response.text)
```

This combination addresses multiple layers of protection:

* **Premium Proxy:** Provides residential IP addresses that are harder to detect and block
* **JavaScript Rendering:** Simulates genuine browser behavior and executes anti-bot detection scripts

## Identifying when you need JavaScript Rendering

You can determine if a website requires JavaScript Rendering by comparing the initial HTML with what you see in the browser:

```python Python theme={null}
import requests

# Test without JavaScript Rendering
url = 'https://httpbin.io/anything'
response_standard = requests.get('https://api.zenrows.com/v1/', params={
    'url': url,
    'apikey': 'YOUR_ZENROWS_API_KEY',
})

# Test with JavaScript Rendering
response_js = requests.get('https://api.zenrows.com/v1/', params={
    'url': url,
    'apikey': 'YOUR_ZENROWS_API_KEY',
    'js_render': 'true',
})

# Compare content lengths
print(f"Standard HTML length: {len(response_standard.text)}")
print(f"JS-rendered HTML length: {len(response_js.text)}")

# If JS-rendered content is significantly longer, you need JavaScript Rendering
```

## Troubleshooting

### Common issues and solutions

| Issue                               | Cause                             | Solution                                              |
| ----------------------------------- | --------------------------------- | ----------------------------------------------------- |
| Content still missing or incomplete | Page needs more time to load      | Increase `wait` time or use `wait_for` parameter      |
| Still getting blocked               | Need residential IPs              | Add `premium_proxy=true`                              |
| Slow response times                 | Browser processing overhead       | Use JavaScript Rendering only when necessary          |
| Higher costs than expected          | Using both features unnecessarily | Implement progressive enhancement strategy            |
| Advanced bot detection              | Sophisticated fingerprinting      | Combine JS Rendering + Premium Proxy + Custom Headers |

### Debugging protection bypassing

When you're still getting blocked despite using JavaScript Rendering:

<Steps>
  <Step title="Add Premium Proxy for residential IPs">
    ```python Python theme={null}
    params = {
        'js_render': 'true',
        'premium_proxy': 'true',
    }
    ```
  </Step>

  <Step title="Increase wait time or wait for a specific selector for detection scripts">
    ```python Python theme={null}
    params = {
        'js_render': 'true',
        'premium_proxy': 'true',
        'wait': '5000',
    }
    ```

    <Tip>
      See more on the [Wait](/universal-scraper-api/features/wait) and [Wait\_for](/universal-scraper-api/features/wait-for) documentation pages
    </Tip>
  </Step>

  <Step title="Add realistic headers">
    ```python Python theme={null}
    params = {
        'js_render': 'true',
        'premium_proxy': 'true',
        'wait': '5000',
    }
    headers = {
        'referer': 'https://www.google.com'
    }
    ```
  </Step>

  <Step title="Return the original status from the website">
    ```python Python theme={null}
    params = {
        'js_render': 'true',
        'original_status': 'true',
    }
    ```

    Returning the original status helps you identify what HTML status code the website is returning.

    <Tip>
      See more about the `original_status` parameter [here](/universal-scraper-api/features/other#original-http-code)
    </Tip>
  </Step>
</Steps>

## Pricing

JavaScript Rendering costs 5 times the standard request rate due to the additional computational resources required for browser processing.

<Tip>
  You can monitor your ZenRows usage in multiple ways to stay informed about your account activity and prevent unexpected overages.

  **Dashboard monitoring**: View real-time usage statistics, remaining requests, success rates, and request history on your [Analytics Page](https://app.zenrows.com/analytics/scraper-api). You can also set up usage alerts in your [notification settings](https://app.zenrows.com/account/notifications) to receive notifications when you approach your limits.

  **Programmatic monitoring**: For automated monitoring in your applications, call the `/v1/subscriptions/self/details` endpoint with your API key in the `X-API-Key` header. This returns real-time usage data that you can integrate into your monitoring systems. [Learn more about the usage endpoint](https://docs.zenrows.com/universal-scraper-api/features/other#plan-usage).

  **Response header monitoring**: Track your concurrency usage through response headers included with each request:

  * `Concurrency-Limit`: Your maximum concurrent requests
  * `Concurrency-Remaining`: Available concurrent request slots
  * `X-Request-Cost`: Cost of the current request
</Tip>

## Frequently Asked Questions (FAQ)

<Accordion title="Which features require the JavaScript Rendering?">
  Several features rely on `js_render` being set to true. These include:

  * [Wait:](/universal-scraper-api/features/wait) Introduces a delay before proceeding with the request. Useful for scenarios where you need to allow time for JavaScript to load content.
  * [Wait For:](/universal-scraper-api/features/wait-for) Waits for a specific element to appear on the page before proceeding. When used with `js_render`, this parameter will cause the request to fail if the selector is not found.
  * [JSON Response:](/universal-scraper-api/features/json-response) Retrieves the rendered page content in JSON format, including data loaded dynamically via JavaScript.
  * [Block Resources:](/universal-scraper-api/features/block-resources) Block specific types of resources from being loaded.
  * [JavaScript Instructions:](/universal-scraper-api/features/js-instructions) Allows you to execute custom JavaScript code on the page. This includes additional parameters.
  * [Screenshot:](/universal-scraper-api/features/output#page-screenshot) Capture an above-the-fold screenshot of the target page by adding `screenshot=true` to the request.
</Accordion>

<Accordion title="When should I use JavaScript Rendering vs standard requests?">
  Use JavaScript Rendering when the content you need is loaded dynamically via JavaScript, when dealing with single-page applications, or when facing advanced anti-bot protection that analyzes browser behavior. If the content is present in the initial HTML response and the site has no protection, standard requests are more cost-effective.
</Accordion>

<Accordion title="Should I always combine JavaScript Rendering with Premium Proxy?">
  For the highest success rate on protected websites, yes. However, this combination costs 25x the standard rate. Start with individual features and combine them only when necessary. Use progressive enhancement to find the most cost-effective approach for each target.
</Accordion>

<Accordion title="Why does JavaScript Rendering help bypass protections?">
  JavaScript Rendering simulates a real browser environment, executing anti-bot detection scripts and providing browser-specific APIs that automated tools typically lack. This makes requests appear more like genuine user traffic, helping bypass sophisticated behavioral analysis systems.
</Accordion>

<Accordion title="What happens if both features still don't work?">
  If the combination of JavaScript Rendering and Premium Proxy doesn't work, the website likely uses very advanced protection. Try adding longer wait times, custom headers, or contact ZenRows support for assistance with particularly challenging targets.
</Accordion>

<Accordion title="Can I use different countries with this combination?">
  Yes, you can specify proxy countries when using both features by adding the `proxy_country` parameter. This can help access geo-restricted content while maintaining the protection benefits of both features.
</Accordion>
