> ## 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.

> ## Agent Instructions
> The Zenrows API (Fetch, Extract via extract=auto, and Batch) is described in OpenAPI 3.1 at https://www.zenrows.com/openapi.json and listed in the RFC 9727 API catalog at https://www.zenrows.com/.well-known/api-catalog. Use the spec for exact parameter names and types.

# Scrape JavaScript-rendered pages

> Get the full content of JavaScript-heavy pages and single-page apps with Zenrows Fetch, after everything has loaded.

Many pages ship an empty shell and fill it with JavaScript after load. Send them to Fetch with `js_render=true` and `wait_for` set to the element you need, and you get the HTML after the content has arrived.

**Uses:** [Fetch](/fetch/api-reference) with [`js_render`](/fetch/features/js-rendering), [`wait_for`](/fetch/features/wait-for), [`wait`](/fetch/features/wait) and [`block_resources`](/fetch/features/block-resources)

## Ask your AI agent

With the [Zenrows MCP server](/mcp/overview) connected to Claude, Cursor or any MCP client, paste this prompt. Your agent picks the right Zenrows tools and settings on its own.

```text theme={"dark"}
Use Zenrows to read https://www.scrapingcourse.com/javascript-rendering after the products
have loaded, and list every product name with its price.
```

## Render the page and wait for the content

The product grid on this page is filled by a background call after the page loads. `js_render=true` runs the page in a browser, and `wait_for=.product-name` holds the capture until the first product name is in the DOM. `css_extractor` then returns only the names and prices as JSON.

<CodeGroup>
  ```python Python (Requests) theme={"dark"}
  import json
  import requests

  params = {
      "apikey": "YOUR_ZENROWS_API_KEY",
      "url": "https://www.scrapingcourse.com/javascript-rendering",
      "js_render": "true",
      "wait_for": ".product-name",
      "css_extractor": json.dumps({"names": ".product-name", "prices": ".product-price"}),
  }

  response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
  response.raise_for_status()

  data = response.json()
  for name, price in list(zip(data["names"], data["prices"]))[:5]:
      print(f"{name}: {price}")
  print("Credits billed:", response.headers["X-Request-Credits"])
  ```

  ```javascript Node.js theme={"dark"}
  const params = new URLSearchParams({
    apikey: "YOUR_ZENROWS_API_KEY",
    url: "https://www.scrapingcourse.com/javascript-rendering",
    js_render: "true",
    wait_for: ".product-name",
    css_extractor: JSON.stringify({ names: ".product-name", prices: ".product-price" }),
  });

  const response = await fetch(`https://api.zenrows.com/v1/?${params}`, {
    signal: AbortSignal.timeout(180_000),
  });
  if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);

  const data = await response.json();
  data.names.slice(0, 5).forEach((name, i) => console.log(`${name}: ${data.prices[i]}`));
  console.log("Credits billed:", response.headers.get("X-Request-Credits"));
  ```

  ```bash cURL theme={"dark"}
  curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.scrapingcourse.com%2Fjavascript-rendering&js_render=true&wait_for=.product-name&css_extractor=%7B%22names%22%3A%22.product-name%22%2C%22prices%22%3A%22.product-price%22%7D"
  ```
</CodeGroup>

Drop `css_extractor` to get the full rendered HTML instead.

From the CLI, the same render and wait print the rendered products as markdown. `--wait-for` runs in manual mode with `--js-render`:

```bash CLI theme={"dark"}
zenrows fetch https://www.scrapingcourse.com/javascript-rendering --manual --js-render --wait-for .product-name --output markdown
```

## Example output

```text theme={"dark"}
Chaz Kangeroo Hoodie: $52
Teton Pullover Hoodie: $70
Bruno Compete Hoodie: $63
Frankie  Sweatshirt: $60
Hollister Backyard Sweatshirt: $52
Credits billed: 5
```

## Wait a fixed time instead

When there is no single element to wait for, for example a chart that animates in, add `wait` in milliseconds. Zenrows waits that long after the page loads before capturing it.

```python Python (Requests) theme={"dark"}
import json
import requests

params = {
    "apikey": "YOUR_ZENROWS_API_KEY",
    "url": "https://www.scrapingcourse.com/javascript-rendering",
    "js_render": "true",
    "wait": "3000",
    "css_extractor": json.dumps({"names": ".product-name"}),
}

response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
response.raise_for_status()
print(len(response.json()["names"]), "products")
```

Output: `12 products`. Prefer `wait_for` when you can name an element: it returns as soon as the element appears, while `wait` always spends the full time.

## Block resources to render faster

Images, fonts and stylesheets rarely matter for data. `block_resources` skips them during the render, which makes the page load faster. Keep `script`, `xhr` and `fetch` unblocked when the content arrives through JavaScript.

```python Python (Requests) theme={"dark"}
import json
import requests

params = {
    "apikey": "YOUR_ZENROWS_API_KEY",
    "url": "https://www.scrapingcourse.com/javascript-rendering",
    "js_render": "true",
    "wait_for": ".product-name",
    "block_resources": "image,media,font,stylesheet",
    "css_extractor": json.dumps({"names": ".product-name"}),
}

response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
response.raise_for_status()
print(len(response.json()["names"]), "products")
```

Output: `12 products`, the same data with fewer downloads.

## Customize it

| Change | How |
| - | - |
| Let Zenrows decide when to render | Use `mode=auto`, see [Adaptive Stealth Mode](/fetch/features/adaptive-stealth-mode). Switch to `js_render` with `wait_for` when you need to time the capture |
| Protected site that also renders with JavaScript | Add `premium_proxy=true`, see [Scrape pages protected by anti-bot systems](/academy/fetch/protected-sites) |
| Click "Load more" or scroll first | Add `js_instructions`, see [Click, fill, and scroll before you scrape](/academy/fetch/page-interactions) |
| Read the JSON the page loads in the background | Add `json_response=true`, see [Capture the network requests behind a page](/academy/capturing-network-requests) |
| Markdown for an LLM | Add `response_type=markdown`, see [Turn any web page into LLM-ready markdown](/academy/fetch/markdown-for-llms) |

## Troubleshooting

* **The response comes back but the data is empty**: the capture happened before the content loaded. Add `wait_for` with a selector that appears once the content is in, like `.product-name` here.
* **`422` after adding `wait_for`**: the selector never appeared. Open the page in your browser, inspect the element and copy a selector from the rendered DOM, not from the page source. See [Wait for selector](/fetch/features/wait-for#troubleshooting).
* **`mode=auto` returns the page before the late content loads**: the page answered fast on its first try. Use `js_render=true` with `wait_for` for that page, so the capture waits for the content.
* **Part of the page is missing with `block_resources`**: a blocked resource type carries the content. Remove `xhr`, `fetch` or `script` from the list.
* **`wait` or `wait_for` has no effect**: both apply to rendered requests. Add `js_render=true` to the same request.

## Related recipes

<CardGroup cols={2}>
  <Card title="Click, fill, and scroll before you scrape" icon="click" href="/academy/fetch/page-interactions">
    Load more items, scroll a feed or submit a search first.
  </Card>

  <Card title="Capture the network requests behind a page" icon="network" href="/academy/capturing-network-requests">
    Read the JSON behind the page instead of its HTML.
  </Card>

  <Card title="Scrape pages protected by anti-bot systems" icon="shield-check" href="/academy/fetch/protected-sites">
    Let Adaptive Stealth Mode pick the right configuration per page.
  </Card>

  <Card title="Capture screenshots, PDFs, and files" icon="camera" href="/academy/fetch/screenshots-and-files">
    Save the rendered page as an image or a PDF.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.