@lownoise-studio/rendershield 0.3.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -32
- package/CONTRIBUTING.md +41 -0
- package/README.md +209 -144
- package/SECURITY.md +25 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +27 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/build.d.ts +2 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +10 -9
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/verify.d.ts +19 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +57 -64
- package/dist/commands/verify.js.map +1 -1
- package/dist/core/generateRobots.d.ts +3 -0
- package/dist/core/generateRobots.d.ts.map +1 -0
- package/dist/core/generateSitemap.d.ts +3 -0
- package/dist/core/generateSitemap.d.ts.map +1 -0
- package/dist/core/generateWorker.d.ts +3 -0
- package/dist/core/generateWorker.d.ts.map +1 -0
- package/dist/core/generateWorker.js +75 -75
- package/dist/core/loadConfig.d.ts +3 -0
- package/dist/core/loadConfig.d.ts.map +1 -0
- package/dist/core/loadConfig.js +68 -25
- package/dist/core/loadConfig.js.map +1 -1
- package/dist/core/loadMarkdown.d.ts +3 -0
- package/dist/core/loadMarkdown.d.ts.map +1 -0
- package/dist/core/loadMarkdown.js +4 -3
- package/dist/core/loadMarkdown.js.map +1 -1
- package/dist/core/renderHtml.d.ts +3 -0
- package/dist/core/renderHtml.d.ts.map +1 -0
- package/dist/core/renderHtml.js +25 -10
- package/dist/core/renderHtml.js.map +1 -1
- package/dist/core/validateOutput.d.ts +28 -0
- package/dist/core/validateOutput.d.ts.map +1 -0
- package/dist/core/validateOutput.js +7 -1
- package/dist/core/validateOutput.js.map +1 -1
- package/dist/errors.d.ts +14 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +24 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/docs/deploy-cloudflare.md +40 -14
- package/package.json +24 -2
- package/src/cli.ts +86 -75
- package/src/commands/build.ts +199 -185
- package/src/commands/verify.ts +266 -236
- package/src/core/generateWorker.ts +97 -97
- package/src/core/loadConfig.ts +261 -173
- package/src/core/loadMarkdown.ts +9 -3
- package/src/core/renderHtml.ts +36 -12
- package/src/core/validateOutput.ts +335 -328
- package/src/errors.ts +48 -0
- package/src/index.ts +40 -0
- package/src/types.ts +4 -1
|
@@ -37,6 +37,12 @@ At a high level:
|
|
|
37
37
|
- Known crawlers are routed to prerendered HTML files
|
|
38
38
|
- All other traffic passes through to your SPA unchanged
|
|
39
39
|
|
|
40
|
+
Every response from the Worker includes an **x-rendershield** header so routing is observable:
|
|
41
|
+
|
|
42
|
+
- **pass-through** — request was not rewritten (human or path not in rewrite bases)
|
|
43
|
+
- **bot-hit** — bot request was rewritten and prerendered HTML was served
|
|
44
|
+
- **bot-fallback** — bot request was rewritten but the origin returned non-200; Worker fell back to the SPA
|
|
45
|
+
|
|
40
46
|
If prerendered output is missing or incomplete, RenderShield fails the build.
|
|
41
47
|
|
|
42
48
|
---
|
|
@@ -104,7 +110,7 @@ The Worker must be able to fetch prerendered files.
|
|
|
104
110
|
|
|
105
111
|
You need a static origin that serves paths like:
|
|
106
112
|
|
|
107
|
-
- /
|
|
113
|
+
- /blog/example/index.html (or your configured route base)
|
|
108
114
|
- /sitemap.xml
|
|
109
115
|
- /robots.txt
|
|
110
116
|
|
|
@@ -132,36 +138,56 @@ Submit the sitemap URL in Google Search Console.
|
|
|
132
138
|
|
|
133
139
|
## Verification
|
|
134
140
|
|
|
135
|
-
|
|
141
|
+
### Production verification (recommended)
|
|
142
|
+
|
|
143
|
+
After deploying the Worker and hosting prerendered output, run:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
rendershield verify --prod https://yourdomain.com
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
This command:
|
|
136
150
|
|
|
137
|
-
|
|
151
|
+
- Fetches the URL as Googlebot
|
|
152
|
+
- Asserts the response has **x-rendershield: bot-hit** (proving the Worker served prerendered HTML)
|
|
153
|
+
- Validates metadata, JSON-LD, and article content
|
|
154
|
+
- Exits with code 1 if the header is missing, is `bot-fallback`, or the contract fails
|
|
138
155
|
|
|
139
|
-
|
|
156
|
+
If it passes, crawlers are receiving the prerendered HTML.
|
|
140
157
|
|
|
141
|
-
|
|
158
|
+
### Manual checks (optional)
|
|
142
159
|
|
|
143
|
-
|
|
160
|
+
Check the response header:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
curl -I -H "User-Agent: Googlebot" https://yourdomain.com/blog/example
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
You should see **x-rendershield: bot-hit**. If debug headers are enabled in config, you may also see:
|
|
144
167
|
|
|
145
168
|
- X-Bot-Detected: true
|
|
146
169
|
- X-Prerender: true
|
|
147
|
-
- X-Final-Path: /
|
|
170
|
+
- X-Final-Path: /blog/example/index.html
|
|
171
|
+
|
|
172
|
+
If **x-rendershield** is missing or **bot-fallback**:
|
|
148
173
|
|
|
149
|
-
If these headers are missing:
|
|
150
174
|
- The Worker route may not be attached
|
|
151
175
|
- The Cloudflare proxy may be disabled
|
|
152
|
-
-
|
|
153
|
-
|
|
154
|
-
---
|
|
176
|
+
- The prerendered origin may be returning non-200 for that path
|
|
155
177
|
|
|
156
|
-
|
|
178
|
+
### Content comparison
|
|
157
179
|
|
|
158
180
|
Human request (SPA response):
|
|
159
181
|
|
|
160
|
-
|
|
182
|
+
```bash
|
|
183
|
+
curl -s https://yourdomain.com/blog/example | grep -i "<title>"
|
|
184
|
+
```
|
|
161
185
|
|
|
162
186
|
Crawler request (prerendered HTML):
|
|
163
187
|
|
|
164
|
-
|
|
188
|
+
```bash
|
|
189
|
+
curl -s -H "User-Agent: Googlebot" https://yourdomain.com/blog/example | grep -i "<title>"
|
|
190
|
+
```
|
|
165
191
|
|
|
166
192
|
The crawler response should contain the prerendered, route-specific title.
|
|
167
193
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lownoise-studio/rendershield",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Boring bot-aware prerendering: real HTML for bots, SPA for humans.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"prerender",
|
|
12
|
+
"seo",
|
|
13
|
+
"bots",
|
|
14
|
+
"spa",
|
|
15
|
+
"static-site",
|
|
16
|
+
"cloudflare-workers",
|
|
17
|
+
"markdown"
|
|
18
|
+
],
|
|
7
19
|
"bin": {
|
|
8
20
|
"rendershield": "dist/cli.js"
|
|
9
21
|
},
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./package.json": "./package.json"
|
|
30
|
+
},
|
|
10
31
|
"scripts": {
|
|
11
32
|
"build": "tsc -p tsconfig.json",
|
|
12
33
|
"dev": "node --enable-source-maps dist/cli.js",
|
|
@@ -19,10 +40,11 @@
|
|
|
19
40
|
"dist/**",
|
|
20
41
|
"src/**",
|
|
21
42
|
"docs/**",
|
|
22
|
-
"content/**",
|
|
23
43
|
"DEPLOY.md",
|
|
24
44
|
"README.md",
|
|
25
45
|
"CHANGELOG.md",
|
|
46
|
+
"CONTRIBUTING.md",
|
|
47
|
+
"SECURITY.md",
|
|
26
48
|
"LICENSE"
|
|
27
49
|
],
|
|
28
50
|
"repository": {
|
package/src/cli.ts
CHANGED
|
@@ -1,75 +1,86 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
import { cmdInit } from "./commands/init.js";
|
|
4
|
-
import { cmdBuild } from "./commands/build.js";
|
|
5
|
-
import { cmdVerify } from "./commands/verify.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
rendershield
|
|
18
|
-
rendershield
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
verify
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { cmdInit } from "./commands/init.js";
|
|
4
|
+
import { cmdBuild } from "./commands/build.js";
|
|
5
|
+
import { cmdVerify } from "./commands/verify.js";
|
|
6
|
+
import { formatCliError, renderShieldError } from "./errors.js";
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const pkg = require("../package.json") as { version?: string };
|
|
10
|
+
const VERSION = pkg.version ?? "0.0.0";
|
|
11
|
+
|
|
12
|
+
function printHelp() {
|
|
13
|
+
console.log(`
|
|
14
|
+
RenderShield v${VERSION} — boring bot-aware prerendering.
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
rendershield init
|
|
18
|
+
rendershield build
|
|
19
|
+
rendershield verify [--prod <url>]
|
|
20
|
+
|
|
21
|
+
verify Print curl commands for local/build output.
|
|
22
|
+
verify --prod Fetch URL as bot and human; verify bot sees full HTML and contract fields.
|
|
23
|
+
Requires x-rendershield: bot-hit from the Worker.
|
|
24
|
+
|
|
25
|
+
Notes:
|
|
26
|
+
- Config file: rendershield.config.json
|
|
27
|
+
- Content: content/<collection>/**/*.md (frontmatter required)
|
|
28
|
+
- Output: dist-prerender/
|
|
29
|
+
`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function main() {
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
const cmd = args[0]?.trim();
|
|
35
|
+
|
|
36
|
+
if (!cmd || cmd === "-h" || cmd === "--help") {
|
|
37
|
+
printHelp();
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
if (cmd === "-V" || cmd === "--version") {
|
|
41
|
+
console.log(VERSION);
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
if (cmd === "init") {
|
|
47
|
+
await cmdInit();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (cmd === "build") {
|
|
51
|
+
await cmdBuild();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (cmd === "verify") {
|
|
55
|
+
const verifyArgs = args.slice(1);
|
|
56
|
+
if (verifyArgs.includes("-h") || verifyArgs.includes("--help")) {
|
|
57
|
+
printHelp();
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
const prodIdx = verifyArgs.indexOf("--prod");
|
|
61
|
+
if (prodIdx >= 0) {
|
|
62
|
+
const prodUrl = verifyArgs[prodIdx + 1]?.trim();
|
|
63
|
+
if (!prodUrl || prodUrl.startsWith("-")) {
|
|
64
|
+
throw renderShieldError(
|
|
65
|
+
"CLI_INVALID_ARGS",
|
|
66
|
+
"verify --prod requires a URL. Example: rendershield verify --prod https://example.com/blog/hello-world"
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
await cmdVerify(undefined, { prodUrl });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
await cmdVerify();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.error(`Unknown command: ${cmd}\n`);
|
|
77
|
+
printHelp();
|
|
78
|
+
process.exit(1);
|
|
79
|
+
} catch (err: unknown) {
|
|
80
|
+
const msg = formatCliError(err);
|
|
81
|
+
console.error(`\nRenderShield error: ${msg}\n`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
main();
|