# Speculation Rules Auditor (`phoenix2810/speculation-rules-auditor`) Actor

Audit a public page for Speculation Rules API usage: inline <script type="speculationrules"> blocks and the Speculation-Rules HTTP header. Returns per-rule analysis, eagerness, issues, score, grade, and Core Web Vitals recommendations.

- **URL**: https://apify.com/phoenix2810/speculation-rules-auditor.md
- **Developed by:** [Sanskar Jaiswal](https://apify.com/phoenix2810) (community)
- **Categories:** SEO tools, Developer tools, Open source
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Speculation Rules Auditor

Audit a public web page for Speculation Rules API usage in one API call. Detects inline `<script type="speculationrules">` blocks and the `Speculation-Rules` HTTP response header, parses each prefetch/prerender rule, validates JSON syntax and rule shapes, flags broad matchers without exclusions, and returns a readiness score, letter grade, and Core Web Vitals recommendations. Built for performance engineers, SEO teams, and site migration QA.

### Use cases

- **Performance engineers** - verify prefetch and prerender rules are present, valid, and correctly scoped before launches
- **SEO teams** - confirm Speculation Rules survive CMS template changes and migrations
- **Site migration QA** - catch missing or malformed speculation rules when moving between frameworks, CDNs, or edge providers
- **Frontend platform teams** - monitor for broad `href_matches: /*` rules without exclusions that risk wasted prerenders and side-effecting GETs
- **Agency consultants** - batch-audit client pages and return structured recommendations

### Input

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `startUrl` | string | yes | - | Public page URL to audit |
| `timeoutSeconds` | integer | no | `10` | Per-request timeout (3-30 seconds) |
| `maxHtmlBytes` | integer | no | `1048576` | Maximum HTML body size to download and parse (16 KB - 2 MB) |

#### Example input

```json
{
  "startUrl": "https://example.com",
  "timeoutSeconds": 10,
  "maxHtmlBytes": 1048576
}
```

### Output

A single dataset item with the full audit:

| Field | Type | Description |
|---|---|---|
| `inputUrl` | string | The URL provided as input |
| `finalUrl` | string | Final URL after redirects |
| `https` | boolean | Whether the final response was served over HTTPS |
| `inlineRuleCount` | integer | Number of inline `<script type="speculationrules">` blocks found |
| `speculationRulesHeader` | string | null | Value of the `Speculation-Rules` HTTP response header, or null when absent |
| `headerRuleUrls` | array | URLs referenced by the `Speculation-Rules` header (resolved against the page), empty when header absent |
| `byAction` | object | Count of rules grouped by action (`prefetch`, `prerender`) |
| `eagernessLevels` | array | Distinct eagerness values used across rules (`immediate`, `eager`, `moderate`, `conservative`) |
| `totalRules` | integer | Total number of prefetch and prerender rule entries parsed |
| `ruleBlocks` | array | Per-block analysis (see below) |
| `issues` | array | Aggregated issue descriptions across all rule blocks |
| `score` | integer | Speculation rules readiness score (0-100) |
| `grade` | string | Letter grade (A+, A, B, C, D, E, F) |
| `checkedAt` | string | ISO 8601 timestamp |
| `recommendations` | array | Actionable recommendations for improving Speculation Rules API readiness |

#### `ruleBlocks` array

Each entry is one inline block or one header reference:

| Field | Type | Description |
|---|---|---|
| `source` | string | `inline` for `<script type="speculationrules">` blocks, `header` for `Speculation-Rules` header references |
| `parseError` | string | null | JSON parse error message, or null when the block parsed successfully |
| `actionsFound` | array | Actions present in the block (`prefetch`, `prerender`) |
| `ruleCount` | integer | Number of rule entries in the block |
| `rules` | array | Per-rule analysis (see below) for inline blocks |
| `issues` | array | Issue descriptions for this block |
| `urls` | array | For header blocks, the resolved rule-file URLs |
| `urlCount` | integer | For header blocks, the number of resolved URLs |

#### `rules` array (within inline `ruleBlocks`)

Each entry describes one prefetch or prerender rule:

| Field | Type | Description |
|---|---|---|
| `action` | string | `prefetch` or `prerender` |
| `hasUrls` | boolean | Whether the rule has a `urls` array |
| `hasWhere` | boolean | Whether the rule has a `where` predicate |
| `urlsCount` | integer | Number of distinct non-empty URLs in `urls` |
| `wherePredicate` | string | null | Recognized predicate in `where` (`href_matches`, `selector_matches`, `and`, `not`), or null |
| `eagerness` | string | null | Eagerness value (`immediate`, `eager`, `moderate`, `conservative`) |
| `defaultEagernessApplied` | boolean | True when eagerness was defaulted because the rule omitted it |
| `issues` | array | Issue descriptions for this rule |

#### Rules checked

The Speculation Rules API uses JSON objects inside `<script type="speculationrules">` blocks or files referenced by the `Speculation-Rules` HTTP response header:

```json
{
  "prefetch": [{ "urls": ["/next"], "eagerness": "eager" }],
  "prerender": [{ "where": { "href_matches": "/*", "not": { "href_matches": "/logout" } }, "eagerness": "moderate" }]
}
```

This actor parses each block and flags:

- Malformed JSON (browsers silently ignore unparseable blocks)
- Rules with neither `urls` nor `where`
- Rules with both `urls` and `where` (spec: `urls` should be exclusive)
- `urls` arrays that are not arrays, empty, contain non-strings, empty strings, or duplicates
- `where` objects with no recognized predicate
- Broad `href_matches: /*` or `*` matchers without a `not` exclusion for state-changing paths (e.g., `/logout`)
- Prerender with `immediate` or `eager` eagerness and more than 3 URLs (exceeds Chrome's recommended 1-2 immediate prerenders)
- Prerender with aggressive eagerness on broad matchers without exclusions
- Invalid `eagerness` values

#### Grading scale

| Score range | Grade |
|---|---|
| 95-100 | A+ |
| 85-94 | A |
| 75-84 | B |
| 65-74 | C |
| 50-64 | D |
| 30-49 | E |
| 0-29 | F |

Scoring rewards genuine adoption (inline blocks, header references, prefetch, prerender, and a graduated prefetch-plus-prerender strategy), then subtracts penalties for each issue. An empty page (no rules and no header) scores 0 because Speculation Rules are opt-in, not a defect.

#### Example output

```json
{
  "inputUrl": "https://example.com",
  "finalUrl": "https://example.com/",
  "https": true,
  "inlineRuleCount": 1,
  "speculationRulesHeader": null,
  "headerRuleUrls": [],
  "byAction": { "prefetch": 1, "prerender": 1 },
  "eagernessLevels": ["eager", "moderate"],
  "totalRules": 2,
  "ruleBlocks": [
    {
      "source": "inline",
      "parseError": null,
      "actionsFound": ["prefetch", "prerender"],
      "ruleCount": 2,
      "rules": [
        { "action": "prefetch", "hasUrls": true, "hasWhere": false, "urlsCount": 2, "wherePredicate": null, "eagerness": "eager", "defaultEagernessApplied": false, "issues": [] },
        { "action": "prerender", "hasUrls": false, "hasWhere": true, "urlsCount": 0, "wherePredicate": "and", "eagerness": "moderate", "defaultEagernessApplied": false, "issues": [] }
      ],
      "issues": []
    }
  ],
  "issues": [],
  "score": 100,
  "grade": "A+",
  "checkedAt": "2026-08-14T12:00:00.000Z",
  "recommendations": [
    "Combine an eager prefetch of common next pages with a moderate prerender for the rest, so prefetches warm before hover-triggered prerenders.",
    "Speculation Rules look well-configured. Schedule this audit periodically to catch CMS template regressions and rule-rot."
  ]
}
```

### Security

- Only public HTTP/HTTPS URLs are accepted
- SSRF protection: localhost, private IPv4/IPv6, and DNS-resolving-to-private IPs are blocked
- URLs with embedded credentials are rejected
- Redirects are manually revalidated before following (max 3)
- HTML body is capped at `maxHtmlBytes` to prevent oversized responses
- The actor does not fetch the URLs referenced by the `Speculation-Rules` header; it only reports them
- No browser automation, no proxies, no cookies stored

### Pricing

Pay per event:

| Event | Price |
|---|---|
| Actor start | $0.005 |
| Page audited | $0.01 |

A single-page audit costs approximately $0.015.

### FAQ

**What is the Speculation Rules API?**
The Speculation Rules API lets pages hint to the browser which future navigations to prefetch or prerender. Rules are JSON objects inside inline `<script type="speculationrules">` elements or external files referenced by the `Speculation-Rules` HTTP response header. Each rule targets a `urls` list or a `where` predicate (with `href_matches` or `selector_matches`) and an `eagerness` level (`immediate`, `eager`, `moderate`, `conservative`).

**How is this different from a resource hints auditor?**
A resource hints auditor inspects `<link rel="preload">`, `preconnect`, `prefetch`, `dns-prefetch`, and `modulepreload` tags. This actor focuses exclusively on the newer Speculation Rules API, which targets document navigations (not individual resource fetches) and uses a JSON rule structure with eagerness levels and `where` predicates. The two are complementary, not overlapping.

**Does the actor fetch the external rule file referenced by the `Speculation-Rules` header?**
No. The actor reports the header value and resolves the referenced URLs against the page, but it does not download the external rule file. Fetching user-controlled secondary URLs would add another SSRF surface for marginal value; the inline block analysis already covers the common deployment.

**Can I audit multiple pages in one run?**
This actor audits one page per run. For bulk audits, schedule multiple runs.

**Does the actor follow redirects?**
Yes, up to 3 redirects. Each redirect target is revalidated for SSRF safety before it is followed.

# Actor input Schema

## `startUrl` (type: `string`):

Public page URL to audit. The actor fetches the HTML once, parses inline <script type="speculationrules"> blocks, and reads the Speculation-Rules HTTP response header. HTTP and HTTPS only. Private IP ranges are blocked.

## `timeoutSeconds` (type: `integer`):

Timeout for the HTTP request.

## `maxHtmlBytes` (type: `integer`):

Maximum HTML body size to download and parse.

## Actor input object example

```json
{
  "startUrl": "https://example.com",
  "timeoutSeconds": 10,
  "maxHtmlBytes": 1048576
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrl": "https://example.com"
};

// Run the Actor and wait for it to finish
const run = await client.actor("phoenix2810/speculation-rules-auditor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "startUrl": "https://example.com" }

# Run the Actor and wait for it to finish
run = client.actor("phoenix2810/speculation-rules-auditor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrl": "https://example.com"
}' |
apify call phoenix2810/speculation-rules-auditor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,phoenix2810/speculation-rules-auditor"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Z52prqeP7pHoC65kU/builds/ln0guqZ2fW7SE8d8p/openapi.json
