Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
changelog:
categories:
- title: Diagnostic improvements
labels:
- enhancement
- title: Fixes
labels:
- bug
- title: Documentation and maintenance
labels:
- documentation
- dependencies
- title: Other changes
labels:
- "*"
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ jobs:
node-version: [20, 22, 24]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm ci --ignore-scripts
- run: npm test
- run: npm run test:coverage
- run: npm run check
- run: npm run pack:check
9 changes: 6 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- uses: actions/setup-node@v7
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
Expand All @@ -37,6 +37,9 @@ jobs:
- name: Check source syntax
run: npm run check

- name: Verify package contents
run: npm run pack:check

- name: Verify release tag matches package version
shell: bash
run: |
Expand All @@ -48,4 +51,4 @@ jobs:
fi

- name: Publish to npm with trusted publishing
run: npm publish --access public
run: npm publish --provenance --access public
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Use Node.js 20 or newer.

```bash
npm test
npm run test:coverage
npm run check
npm run pack:check
```
Expand Down
125 changes: 108 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,60 @@

Open-source diagnostics for checking what public websites return to crawlers.

The CLI inspects returned HTML, compares browser-style and crawler-style responses, and validates common discovery files. It does not render JavaScript, change a website, require a Prerender Buddy account, or predict search rankings, indexing, AI citations, mentions, or traffic.
The CLI inspects returned HTML, compares standard and crawler user-agent HTTP responses, and validates common discovery files. It does not render JavaScript, change a website, require a Prerender Buddy account, or predict search rankings, indexing, AI citations, mentions, or traffic.

This is an early public release. Install it from npm or run it from a local checkout.
Run it without installing:

```bash
npx @prerenderbuddy/cli check https://example.com --user-agent googlebot
```

Or install the command globally:

```bash
npm install --global @prerenderbuddy/cli
prerenderbuddy check https://example.com
```

Example output from the included loading-placeholder fixture:

```text
Prerender Buddy · crawler HTML check · CRITICAL
URL https://example.com/app
Crawler profile Googlebot
HTTP 200
Final URL https://example.com/app
Title Loading application
Description Application loading screen.
H1 Loading application
Readable text 41 characters / 5 words
App-shell signs root div, bundled assets, module scripts

Issues:
- CRITICAL [app_shell]: Returned HTML has limited visible content and multiple JavaScript app-shell signals.
Why: Crawlers that do not execute JavaScript may receive only the application shell.
Evidence: {"readableCharacters":41,"scriptCount":1,"signals":["loading-only visible text","module or bundled application script","root div detected","bundled assets detected","module scripts detected"]}
Next: Inspect the raw response and test whether important page content is present before JavaScript executes.

This checks returned HTML only. It does not predict rankings, indexing, citations, mentions, or traffic.
```

Reproduce that output from a local checkout with `npm run demo:fixture`. The demo injects a static fixture into the normal check and formatting functions; it does not weaken public-URL safety or start a local URL-fetching service.

The finding is a documented heuristic, not proof that a crawler failed. The CLI does not run Chromium, execute page JavaScript, or produce rendered HTML.

## Requirements

- Node.js 20 or newer
- A public HTTP or HTTPS URL

Run without installing:
Other commands:

```bash
npx @prerenderbuddy/cli check https://example.com
npx @prerenderbuddy/cli compare https://example.com --user-agent gptbot
npx @prerenderbuddy/cli files https://example.com
```

Local checkout usage:

```bash
node ./bin/prerenderbuddy.js check https://example.com
node ./bin/prerenderbuddy.js compare https://example.com --user-agent gptbot
node ./bin/prerenderbuddy.js files https://example.com
```

## Commands

### Check crawler-readable HTML
Expand All @@ -57,9 +86,17 @@ The app-shell test is a heuristic. A warning is a reason to inspect the page, no
prerenderbuddy compare https://example.com --user-agent gptbot
```

Compares a browser-style response with the selected crawler response. It flags status, metadata, heading, and material text differences. Different output can be legitimate; the result is evidence to review, not an accusation of cloaking.
Compares a browser-style user-agent HTTP response with the selected crawler user-agent HTTP response. It reports status, metadata, heading, and material text-volume differences separately. Different output can be legitimate; the result is evidence to review, not an accusation of cloaking.

Both sides are ordinary HTTP responses. Neither side executes JavaScript. This is not a raw-versus-browser-rendered comparison, and the package has no browser engine or connection to Prerender Buddy’s private rendering infrastructure.

This is not a raw-versus-browser-rendered comparison. The open-source v0.1 package deliberately has no browser engine or connection to Prerender Buddy’s private rendering infrastructure.
The default text-ratio tolerance is 30% in either direction. Adjust it for a known-variable site:

```bash
prerenderbuddy compare https://example.com --text-ratio-threshold 0.20
```

The comparison normalizes HTML into whitespace-collapsed visible text and reports the exact lengths and metadata values that changed. It does not perform semantic AI comparison or automatically remove cookie notices, timestamps, rotating banners, experiments, personalization, regional content, anti-bot pages, or temporary CDN responses. Review those sources of variation before treating a warning as a regression.

### Validate discovery files

Expand Down Expand Up @@ -101,6 +138,29 @@ Exit codes:

JSON fields are intended to become stable at `1.0.0`. Before then, minor releases may add or refine diagnostic fields.

### Programmatic use

The same diagnostics are exported as dependency-free ESM functions:

```js
import {
analyzeHtml,
checkDiscoveryFiles,
checkUrl,
compareUrl,
} from '@prerenderbuddy/cli';

const page = await checkUrl('https://example.com', { userAgent: 'googlebot' });
const comparison = await compareUrl('https://example.com', {
userAgent: 'gptbot',
textRatioThreshold: 0.2,
});
const files = await checkDiscoveryFiles('https://example.com');
const localAnalysis = analyzeHtml('<main><h1>Example</h1></main>');
```

Network functions retain the same public-URL safety, redirect, timeout, and response-size controls as the CLI.

### GitHub Actions

Copy [`examples/github-actions/crawler-readability.yml`](https://github.com/kopachlager/prerenderbuddy-cli/blob/main/examples/github-actions/crawler-readability.yml)
Expand All @@ -110,7 +170,7 @@ into the target repository as `.github/workflows/crawler-readability.yml`, then
The example:

- checks crawler-readable HTML as Googlebot;
- compares browser-style and GPTBot responses;
- compares browser-style user-agent and GPTBot HTTP responses;
- validates `robots.txt`, `sitemap.xml`, and `llms.txt`;
- fails only on critical findings by default;
- pins the CLI version so updates are reviewed deliberately.
Expand All @@ -131,16 +191,39 @@ Fetched page text is untrusted data. The CLI displays and analyses it; it must n

See [SECURITY.md](./SECURITY.md) for reporting and current limitations.

## When a managed service is not needed
## CLI and hosted service

| Capability | Open-source CLI | Hosted Prerender Buddy |
| --- | --- | --- |
| One-time public URL diagnostics | Yes | Yes |
| Local execution and CI | Yes | No |
| Returned HTML inspection | Yes | Yes |
| JavaScript execution | No | Yes, for managed crawler-ready rendering |
| Scheduled monitoring | No | Yes |
| Baselines, history, and incidents | No | Yes |
| Managed crawler routing | No | Yes |
| Cache operations | No | Yes |
| DNS or proxy onboarding | No | Yes |
| Account required | No | Yes |

The CLI is independently useful for diagnostics. The hosted service operates rendering, routing, monitoring, and cache workflows when testing shows that a production deployment needs them.

### When a managed service is not needed

If important production routes already return complete, consistent HTML to the crawlers you care about, an additional rendering layer may not be needed. Continue testing after framework, hosting, domain, or deployment changes.

## When Prerender Buddy may help
### When Prerender Buddy may help

If production tests find missing, partial, crawler-dependent, or unreliable HTML, Prerender Buddy can provide managed crawler-ready rendering. Its hosted service also provides scheduled monitoring, baselines, incidents, history, cache operations, DNS/proxy onboarding, crawler routing, and support.

The CLI diagnoses a current response. The hosted service operates and monitors the production solution.

## Fixtures and heuristic limits

Deterministic fixtures live in [`test/fixtures`](./test/fixtures). They cover healthy HTML, thin application shells, minimal static pages, canvas applications, loading placeholders, hidden script data, cookie banners, crawler-blocked responses, malformed metadata, and discovery-file errors.

Application-shell detection uses observable inputs: readable character count, empty `root` or `app` mount points, loading-only text, module or bundled scripts, and framework markers. It does not identify a framework failure, simulate verified crawler traffic, or prove that a genuine crawler received the same response.

## Development

```bash
Expand All @@ -156,6 +239,14 @@ The package intentionally starts with no runtime dependencies.

Read [CONTRIBUTING.md](./CONTRIBUTING.md). Keep contributions focused on accurate, reproducible crawler diagnostics. New checks need fixtures, tests, documented limitations, and evidence that they do not duplicate managed-service operations.

## Next steps

- Run the [browser-based crawler checker](https://prerenderbuddy.com/tools/bot-view-checker).
- Read the [technical documentation](https://prerenderbuddy.com/docs).
- Review the [public roadmap](./ROADMAP.md).
- Report reproducible CLI problems in [GitHub Issues](https://github.com/kopachlager/prerenderbuddy-cli/issues).
- Use the [hosted Prerender Buddy service](https://prerenderbuddy.com) when diagnostics show that managed rendering or monitoring is needed.

## License

Apache License 2.0. See [LICENSE](./LICENSE).
24 changes: 24 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Public roadmap

This roadmap describes possible directions, not promised dates or release commitments.

## Diagnostic improvements

- additional transparent crawler profiles supported by public documentation;
- clearer evidence and remediation fields for every finding;
- more deterministic HTML and discovery-file fixtures;
- configurable diagnostic thresholds;
- stable JSON schema work toward `1.0`;
- URL-list or sitemap-driven batch checks;
- richer CI annotations and possible SARIF output.

## Explicitly out of scope

- browser rendering or JavaScript execution;
- private Prerender Buddy API access;
- hosted monitoring, baselines, incidents, or history;
- managed crawler routing or DNS onboarding;
- cache management;
- proxy, queue, billing, or infrastructure deployment.

The CLI will remain usable without an account, authentication, telemetry, or calls to Prerender Buddy production services.
8 changes: 6 additions & 2 deletions bin/prerenderbuddy.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#!/usr/bin/env node

import { runCli } from '../src/cli.js';
import { executionErrorResult, runCli } from '../src/cli.js';

runCli(process.argv.slice(2)).catch((error) => {
process.stderr.write(`Prerender Buddy check failed: ${error.message}\n`);
if (process.argv.includes('--json')) {
process.stdout.write(`${JSON.stringify(executionErrorResult(error), null, 2)}\n`);
} else {
process.stderr.write(`Prerender Buddy check failed: ${error.message}\n`);
}
process.exitCode = 2;
});
10 changes: 5 additions & 5 deletions examples/github-actions/crawler-readability.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,26 @@ jobs:
env:
SITE_URL: https://example.com
steps:
- uses: actions/setup-node@v7
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: "24"

- name: Check crawler-readable HTML
run: >-
npx --yes @prerenderbuddy/cli@0.1.1
npx --yes @prerenderbuddy/cli@0.1.2
check "$SITE_URL"
--user-agent googlebot
--fail-on critical

- name: Compare browser and AI crawler responses
- name: Compare standard and AI crawler HTTP responses
run: >-
npx --yes @prerenderbuddy/cli@0.1.1
npx --yes @prerenderbuddy/cli@0.1.2
compare "$SITE_URL"
--user-agent gptbot
--fail-on critical

- name: Validate discovery files
run: >-
npx --yes @prerenderbuddy/cli@0.1.1
npx --yes @prerenderbuddy/cli@0.1.2
files "$SITE_URL"
--fail-on critical
13 changes: 9 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,30 @@
"LICENSE",
"NOTICE",
"README.md",
"ROADMAP.md",
"SECURITY.md"
],
"scripts": {
"check": "node --check bin/prerenderbuddy.js && node --check src/*.js",
"demo:fixture": "node scripts/demo-fixture.js",
"test": "node --test",
"test:coverage": "node --test --experimental-test-coverage",
"pack:check": "npm pack --dry-run"
"pack:check": "node scripts/verify-package.js"
},
"engines": {
"node": ">=20"
},
"keywords": [
"crawler",
"technical-seo",
"javascript-seo",
"prerendering",
"robots.txt",
"googlebot",
"gptbot",
"robots-txt",
"sitemap",
"llms.txt",
"cli"
"llms-txt",
"seo-cli"
],
"license": "Apache-2.0",
"publishConfig": {
Expand Down
18 changes: 18 additions & 0 deletions scripts/demo-fixture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { readFile } from 'node:fs/promises';
import { checkUrl } from '../src/check.js';
import { formatHuman } from '../src/format.js';

const html = await readFile(
new URL('../test/fixtures/html/loading-placeholder.html', import.meta.url),
'utf8',
);
const result = await checkUrl('https://example.com/app', {
userAgent: 'googlebot',
assertUrlFn: async () => {},
fetchFn: async () => new Response(html, {
status: 200,
headers: { 'content-type': 'text/html; charset=utf-8' },
}),
});

process.stdout.write(`${formatHuman(result)}\n`);
Loading