@lownoise-studio/rendershield 0.1.4
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/DEPLOY.md +185 -0
- package/LICENSE +21 -0
- package/README.md +138 -0
- package/dist/cli.js +50 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/build.js +58 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/init.js +102 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/verify.js +100 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/core/generateRobots.js +14 -0
- package/dist/core/generateRobots.js.map +1 -0
- package/dist/core/generateSitemap.js +31 -0
- package/dist/core/generateSitemap.js.map +1 -0
- package/dist/core/generateWorker.js +77 -0
- package/dist/core/generateWorker.js.map +1 -0
- package/dist/core/loadConfig.js +60 -0
- package/dist/core/loadConfig.js.map +1 -0
- package/dist/core/loadMarkdown.js +63 -0
- package/dist/core/loadMarkdown.js.map +1 -0
- package/dist/core/renderHtml.js +69 -0
- package/dist/core/renderHtml.js.map +1 -0
- package/dist/core/validateOutput.js +121 -0
- package/dist/core/validateOutput.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/deploy-cloudflare.md +204 -0
- package/package.json +43 -0
- package/src/cli.ts +54 -0
- package/src/commands/build.ts +72 -0
- package/src/commands/init.ts +112 -0
- package/src/commands/verify.ts +114 -0
- package/src/core/generateRobots.ts +18 -0
- package/src/core/generateSitemap.ts +38 -0
- package/src/core/generateWorker.ts +80 -0
- package/src/core/loadConfig.ts +74 -0
- package/src/core/loadMarkdown.ts +82 -0
- package/src/core/renderHtml.ts +76 -0
- package/src/core/validateOutput.ts +148 -0
- package/src/types/markdown-it.d.ts +20 -0
- package/src/types.ts +52 -0
package/DEPLOY.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Deploying RenderShield
|
|
2
|
+
|
|
3
|
+
This document describes how to deploy prerendered output generated by RenderShield so that crawlers receive complete static HTML while users continue to receive your application.
|
|
4
|
+
|
|
5
|
+
RenderShield does not run your app.
|
|
6
|
+
It only produces files and optional routing logic.
|
|
7
|
+
|
|
8
|
+
If RenderShield builds successfully, the output is safe to deploy.
|
|
9
|
+
|
|
10
|
+
1. Build
|
|
11
|
+
|
|
12
|
+
From the RenderShield project root:
|
|
13
|
+
|
|
14
|
+
npm install
|
|
15
|
+
npm run build
|
|
16
|
+
|
|
17
|
+
This generates prerendered output in:
|
|
18
|
+
|
|
19
|
+
dist-prerender/
|
|
20
|
+
|
|
21
|
+
Typical contents:
|
|
22
|
+
|
|
23
|
+
dist-prerender/
|
|
24
|
+
├─ content/
|
|
25
|
+
│ └─ example/
|
|
26
|
+
│ └─ index.html
|
|
27
|
+
├─ sitemap.xml
|
|
28
|
+
├─ robots.txt
|
|
29
|
+
└─ worker.js
|
|
30
|
+
|
|
31
|
+
The exact structure depends on your configured routes.
|
|
32
|
+
|
|
33
|
+
2. Hosting the prerendered output
|
|
34
|
+
|
|
35
|
+
You must host the contents of dist-prerender/ on a static origin that can serve files directly.
|
|
36
|
+
|
|
37
|
+
The static origin must be able to serve:
|
|
38
|
+
|
|
39
|
+
/content/example/index.html
|
|
40
|
+
/sitemap.xml
|
|
41
|
+
/robots.txt
|
|
42
|
+
|
|
43
|
+
Common options include:
|
|
44
|
+
|
|
45
|
+
a static hosting bucket
|
|
46
|
+
|
|
47
|
+
a CDN-backed static site
|
|
48
|
+
|
|
49
|
+
the same host as your SPA, under a different path or subdomain
|
|
50
|
+
|
|
51
|
+
RenderShield does not require a specific host.
|
|
52
|
+
|
|
53
|
+
3. Routing crawlers to prerendered HTML (optional but recommended)
|
|
54
|
+
|
|
55
|
+
To ensure crawlers receive prerendered HTML while humans receive your app, you must route requests based on user-agent.
|
|
56
|
+
|
|
57
|
+
RenderShield provides an optional Cloudflare Worker (worker.js) for this purpose.
|
|
58
|
+
|
|
59
|
+
The worker:
|
|
60
|
+
|
|
61
|
+
detects crawler user agents
|
|
62
|
+
|
|
63
|
+
rewrites matching routes to prerendered index.html
|
|
64
|
+
|
|
65
|
+
forwards human traffic unchanged
|
|
66
|
+
|
|
67
|
+
Humans keep the SPA.
|
|
68
|
+
Crawlers get static HTML.
|
|
69
|
+
|
|
70
|
+
4. Deploying the Cloudflare Worker
|
|
71
|
+
|
|
72
|
+
Create a Cloudflare account (free plan is sufficient).
|
|
73
|
+
|
|
74
|
+
Deploy worker.js as a Worker.
|
|
75
|
+
|
|
76
|
+
Attach the Worker to your domain with a route:
|
|
77
|
+
|
|
78
|
+
yourdomain.com/*
|
|
79
|
+
www.yourdomain.com/
|
|
80
|
+
*
|
|
81
|
+
|
|
82
|
+
Without a route, the Worker will never run.
|
|
83
|
+
|
|
84
|
+
Ensure the DNS record for your domain is proxied (orange cloud ON).
|
|
85
|
+
|
|
86
|
+
5. SSL configuration
|
|
87
|
+
|
|
88
|
+
In Cloudflare:
|
|
89
|
+
|
|
90
|
+
Set SSL/TLS mode to:
|
|
91
|
+
|
|
92
|
+
Full
|
|
93
|
+
or
|
|
94
|
+
Full (Strict)
|
|
95
|
+
|
|
96
|
+
Do not use “Flexible”.
|
|
97
|
+
|
|
98
|
+
Incorrect SSL mode can cause redirect loops that prevent crawlers from reaching prerendered content.
|
|
99
|
+
|
|
100
|
+
6. Sitemap and robots
|
|
101
|
+
|
|
102
|
+
Upload these files from dist-prerender/ to your static origin:
|
|
103
|
+
|
|
104
|
+
sitemap.xml
|
|
105
|
+
robots.txt
|
|
106
|
+
|
|
107
|
+
Then submit your sitemap URL to your search engine consoles as needed.
|
|
108
|
+
|
|
109
|
+
Example robots.txt:
|
|
110
|
+
|
|
111
|
+
User-agent: *
|
|
112
|
+
Allow: /
|
|
113
|
+
Sitemap: https://yourdomain.com/sitemap.xml
|
|
114
|
+
|
|
115
|
+
7. Verification
|
|
116
|
+
|
|
117
|
+
RenderShield includes a verification command that prints smoke tests.
|
|
118
|
+
|
|
119
|
+
Run:
|
|
120
|
+
|
|
121
|
+
node dist/cli.js verify
|
|
122
|
+
|
|
123
|
+
This prints three checks:
|
|
124
|
+
|
|
125
|
+
Human request (should return your app shell)
|
|
126
|
+
|
|
127
|
+
Crawler request (should return prerendered HTML)
|
|
128
|
+
|
|
129
|
+
Debug headers (confirms worker routing)
|
|
130
|
+
|
|
131
|
+
If verification passes, deployment is correct.
|
|
132
|
+
|
|
133
|
+
8. What RenderShield guarantees
|
|
134
|
+
|
|
135
|
+
RenderShield guarantees only this:
|
|
136
|
+
|
|
137
|
+
If a page builds successfully, the generated HTML:
|
|
138
|
+
|
|
139
|
+
contains real content
|
|
140
|
+
|
|
141
|
+
includes required metadata
|
|
142
|
+
|
|
143
|
+
is readable by crawlers
|
|
144
|
+
|
|
145
|
+
RenderShield does not guarantee:
|
|
146
|
+
|
|
147
|
+
rankings
|
|
148
|
+
|
|
149
|
+
traffic
|
|
150
|
+
|
|
151
|
+
indexing speed
|
|
152
|
+
|
|
153
|
+
AI citations
|
|
154
|
+
|
|
155
|
+
It guarantees correctness of output.
|
|
156
|
+
|
|
157
|
+
9. Common failure modes
|
|
158
|
+
|
|
159
|
+
If crawlers do not see prerendered content:
|
|
160
|
+
|
|
161
|
+
Worker route is missing or incorrect
|
|
162
|
+
|
|
163
|
+
DNS proxy is OFF
|
|
164
|
+
|
|
165
|
+
Static origin is unreachable
|
|
166
|
+
|
|
167
|
+
SSL mode is misconfigured
|
|
168
|
+
|
|
169
|
+
Prerendered files are not deployed
|
|
170
|
+
|
|
171
|
+
RenderShield verification output will point to these issues.
|
|
172
|
+
|
|
173
|
+
Summary
|
|
174
|
+
|
|
175
|
+
RenderShield separates responsibilities cleanly:
|
|
176
|
+
|
|
177
|
+
Your app serves users
|
|
178
|
+
|
|
179
|
+
RenderShield serves crawlers
|
|
180
|
+
|
|
181
|
+
The edge decides who gets what
|
|
182
|
+
|
|
183
|
+
If it builds, it ships.
|
|
184
|
+
|
|
185
|
+
That is the contract.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lownoise Studio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# RenderShield
|
|
2
|
+
|
|
3
|
+
RenderShield ensures crawlers receive complete, static HTML instead of an empty
|
|
4
|
+
JavaScript shell — without changing how humans experience your app.
|
|
5
|
+
|
|
6
|
+
It prerenders content ahead of time and can route crawler requests to that output
|
|
7
|
+
at the edge, while normal users continue to receive the SPA.
|
|
8
|
+
|
|
9
|
+
No frameworks required.
|
|
10
|
+
No vendor lock-in.
|
|
11
|
+
No guessing what bots see.
|
|
12
|
+
|
|
13
|
+
If a page builds, the HTML is complete. If it isn’t, the build fails.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What problem this solves
|
|
18
|
+
|
|
19
|
+
Many modern apps technically work but are invisible to:
|
|
20
|
+
|
|
21
|
+
- search engines
|
|
22
|
+
- social preview scrapers
|
|
23
|
+
- AI crawlers
|
|
24
|
+
|
|
25
|
+
The content exists only after JavaScript executes.
|
|
26
|
+
Crawlers do not wait.
|
|
27
|
+
|
|
28
|
+
RenderShield fixes this by generating static, bot-readable HTML and refusing
|
|
29
|
+
to ship incomplete output.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## What RenderShield does
|
|
34
|
+
|
|
35
|
+
- Converts structured content into full static HTML pages
|
|
36
|
+
- Injects:
|
|
37
|
+
- title
|
|
38
|
+
- meta description
|
|
39
|
+
- canonical link
|
|
40
|
+
- Open Graph tags
|
|
41
|
+
- Twitter tags
|
|
42
|
+
- JSON-LD (Article)
|
|
43
|
+
- Generates:
|
|
44
|
+
- index.html per route
|
|
45
|
+
- sitemap.xml
|
|
46
|
+
- robots.txt
|
|
47
|
+
- optional Cloudflare Worker
|
|
48
|
+
- Validates output
|
|
49
|
+
- missing title, metadata, or content causes the build to fail
|
|
50
|
+
|
|
51
|
+
If it builds, crawlers will see real content.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## What it does not do
|
|
56
|
+
|
|
57
|
+
- It does not run your application
|
|
58
|
+
- It does not execute JavaScript for bots
|
|
59
|
+
- It does not guess content
|
|
60
|
+
- It does not promise rankings, traffic, or citations
|
|
61
|
+
- It does not replace your SPA
|
|
62
|
+
|
|
63
|
+
It guarantees one thing only:
|
|
64
|
+
that crawlers receive complete HTML.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Quickstart
|
|
69
|
+
|
|
70
|
+
1) Install dependencies
|
|
71
|
+
|
|
72
|
+
Run:
|
|
73
|
+
|
|
74
|
+
npm install
|
|
75
|
+
|
|
76
|
+
2) Initialize RenderShield
|
|
77
|
+
|
|
78
|
+
Run:
|
|
79
|
+
|
|
80
|
+
npm run build
|
|
81
|
+
|
|
82
|
+
This creates:
|
|
83
|
+
- rendershield.config.json
|
|
84
|
+
- sample content
|
|
85
|
+
- a prerender output directory
|
|
86
|
+
|
|
87
|
+
3) Add content
|
|
88
|
+
|
|
89
|
+
Place structured content under:
|
|
90
|
+
|
|
91
|
+
content/
|
|
92
|
+
|
|
93
|
+
4) Build prerendered output
|
|
94
|
+
|
|
95
|
+
Run:
|
|
96
|
+
|
|
97
|
+
node dist/cli.js build
|
|
98
|
+
|
|
99
|
+
Output is written to:
|
|
100
|
+
|
|
101
|
+
dist-prerender/
|
|
102
|
+
|
|
103
|
+
5) Verify output
|
|
104
|
+
|
|
105
|
+
Run:
|
|
106
|
+
|
|
107
|
+
node dist/cli.js verify
|
|
108
|
+
|
|
109
|
+
This prints curl commands you can use to confirm crawler behavior.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Deployment
|
|
114
|
+
|
|
115
|
+
RenderShield is designed to run behind Cloudflare.
|
|
116
|
+
|
|
117
|
+
A Worker routes crawler requests to prerendered HTML while passing all other
|
|
118
|
+
traffic through unchanged.
|
|
119
|
+
|
|
120
|
+
See:
|
|
121
|
+
docs/deploy-cloudflare.md
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Philosophy
|
|
126
|
+
|
|
127
|
+
RenderShield is intentionally boring.
|
|
128
|
+
|
|
129
|
+
It does not attempt to outsmart crawlers or simulate browsers.
|
|
130
|
+
It produces correct HTML and refuses to ship broken output.
|
|
131
|
+
|
|
132
|
+
If crawlers cannot read it, the build fails.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT License
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cmdInit } from "./commands/init.js";
|
|
3
|
+
import { cmdBuild } from "./commands/build.js";
|
|
4
|
+
import { cmdVerify } from "./commands/verify.js";
|
|
5
|
+
function printHelp() {
|
|
6
|
+
console.log(`
|
|
7
|
+
RenderShield (v0) — boring bot-aware prerendering.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
rendershield init
|
|
11
|
+
rendershield build
|
|
12
|
+
rendershield verify
|
|
13
|
+
|
|
14
|
+
Notes:
|
|
15
|
+
- Config file: rendershield.config.json
|
|
16
|
+
- Content: content/blog/*.md (frontmatter required)
|
|
17
|
+
- Output: dist-prerender/
|
|
18
|
+
`);
|
|
19
|
+
}
|
|
20
|
+
async function main() {
|
|
21
|
+
const cmd = process.argv[2]?.trim();
|
|
22
|
+
if (!cmd || cmd === "-h" || cmd === "--help") {
|
|
23
|
+
printHelp();
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
if (cmd === "init") {
|
|
28
|
+
await cmdInit();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (cmd === "build") {
|
|
32
|
+
await cmdBuild();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (cmd === "verify") {
|
|
36
|
+
await cmdVerify();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
console.error(`Unknown command: ${cmd}\n`);
|
|
40
|
+
printHelp();
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
const msg = err?.message ? String(err.message) : String(err);
|
|
45
|
+
console.error(`\nRenderShield error: ${msg}\n`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
main();
|
|
50
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjD,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;CAYb,CAAC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;IAEpC,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC7C,SAAS,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,CAAC;QACH,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;YACnB,MAAM,OAAO,EAAE,CAAC;YAChB,OAAO;QACT,CAAC;QACD,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;YACpB,MAAM,QAAQ,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;YACrB,MAAM,SAAS,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QAED,OAAO,CAAC,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;QAC3C,SAAS,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,CAAC,KAAK,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAAC;QAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { loadConfig } from "../core/loadConfig.js";
|
|
4
|
+
import { loadAllMarkdownDocs } from "../core/loadMarkdown.js";
|
|
5
|
+
import { renderPageHtml } from "../core/renderHtml.js";
|
|
6
|
+
import { generateSitemapXml } from "../core/generateSitemap.js";
|
|
7
|
+
import { generateRobotsTxt } from "../core/generateRobots.js";
|
|
8
|
+
import { generateWorkerJs } from "../core/generateWorker.js";
|
|
9
|
+
import { validatePrerenderHtml } from "../core/validateOutput.js";
|
|
10
|
+
function routeToOutDir(outDirAbs, routePath) {
|
|
11
|
+
// /blog/slug -> outDir/blog/slug/index.html
|
|
12
|
+
const clean = routePath.replace(/^\//, "");
|
|
13
|
+
return path.join(outDirAbs, clean);
|
|
14
|
+
}
|
|
15
|
+
export async function cmdBuild(cwd = process.cwd()) {
|
|
16
|
+
const cfg = await loadConfig(cwd);
|
|
17
|
+
const outDirAbs = path.join(cwd, cfg.output.outDir);
|
|
18
|
+
// Clean output (boring + deterministic)
|
|
19
|
+
await fs.remove(outDirAbs);
|
|
20
|
+
await fs.ensureDir(outDirAbs);
|
|
21
|
+
const docs = await loadAllMarkdownDocs(cfg, cwd);
|
|
22
|
+
if (docs.length === 0) {
|
|
23
|
+
throw new Error("No markdown documents found. Check content paths/patterns.");
|
|
24
|
+
}
|
|
25
|
+
// Generate pages (validate BEFORE writing)
|
|
26
|
+
for (const doc of docs) {
|
|
27
|
+
const pageDir = routeToOutDir(outDirAbs, doc.routePath);
|
|
28
|
+
await fs.ensureDir(pageDir);
|
|
29
|
+
const outFile = path.join(pageDir, "index.html");
|
|
30
|
+
const html = renderPageHtml(cfg, doc);
|
|
31
|
+
validatePrerenderHtml({
|
|
32
|
+
html,
|
|
33
|
+
outFile,
|
|
34
|
+
routePath: doc.routePath,
|
|
35
|
+
});
|
|
36
|
+
await fs.writeFile(outFile, html, "utf8");
|
|
37
|
+
}
|
|
38
|
+
// sitemap.xml
|
|
39
|
+
if (cfg.sitemap.enabled) {
|
|
40
|
+
const sitemapXml = generateSitemapXml(cfg, docs);
|
|
41
|
+
const sitemapPath = path.join(outDirAbs, cfg.sitemap.path.replace(/^\//, ""));
|
|
42
|
+
await fs.writeFile(sitemapPath, sitemapXml, "utf8");
|
|
43
|
+
}
|
|
44
|
+
// robots.txt
|
|
45
|
+
if (cfg.robots.enabled) {
|
|
46
|
+
const robotsTxt = generateRobotsTxt(cfg);
|
|
47
|
+
const robotsPath = path.join(outDirAbs, cfg.robots.path.replace(/^\//, ""));
|
|
48
|
+
await fs.writeFile(robotsPath, robotsTxt, "utf8");
|
|
49
|
+
}
|
|
50
|
+
// worker.js
|
|
51
|
+
if (cfg.worker.enabled) {
|
|
52
|
+
const workerJs = generateWorkerJs(cfg);
|
|
53
|
+
await fs.writeFile(path.join(outDirAbs, "worker.js"), workerJs, "utf8");
|
|
54
|
+
}
|
|
55
|
+
console.log(`Built ${docs.length} pages into ${cfg.output.outDir}/`);
|
|
56
|
+
console.log(`Output includes: ${cfg.sitemap.enabled ? "sitemap.xml " : ""}${cfg.robots.enabled ? "robots.txt " : ""}${cfg.worker.enabled ? "worker.js" : ""}`);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const CONFIG_NAME = "rendershield.config.json";
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
version: 1,
|
|
6
|
+
site: {
|
|
7
|
+
canonicalBase: "https://example.com",
|
|
8
|
+
siteName: "Example Site",
|
|
9
|
+
defaultOgImage: "https://example.com/og/default.jpg",
|
|
10
|
+
authorName: "Your Name"
|
|
11
|
+
},
|
|
12
|
+
content: {
|
|
13
|
+
markdown: {
|
|
14
|
+
baseDir: "content",
|
|
15
|
+
collections: [
|
|
16
|
+
{
|
|
17
|
+
name: "pages",
|
|
18
|
+
pattern: "pages/**/*.md",
|
|
19
|
+
routeBase: "/pages",
|
|
20
|
+
schemaType: "Article"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
output: {
|
|
26
|
+
outDir: "dist-prerender",
|
|
27
|
+
prettyHtml: true
|
|
28
|
+
},
|
|
29
|
+
sitemap: {
|
|
30
|
+
enabled: true,
|
|
31
|
+
path: "/sitemap.xml"
|
|
32
|
+
},
|
|
33
|
+
robots: {
|
|
34
|
+
enabled: true,
|
|
35
|
+
path: "/robots.txt"
|
|
36
|
+
},
|
|
37
|
+
worker: {
|
|
38
|
+
enabled: true,
|
|
39
|
+
lovableOrigin: "https://YOUR_SITE.lovable.app",
|
|
40
|
+
rewriteRouteBases: ["/pages/"],
|
|
41
|
+
botUserAgentPatterns: [
|
|
42
|
+
"googlebot",
|
|
43
|
+
"bingbot",
|
|
44
|
+
"gptbot",
|
|
45
|
+
"claudebot",
|
|
46
|
+
"perplexitybot",
|
|
47
|
+
"twitterbot",
|
|
48
|
+
"facebookexternalhit",
|
|
49
|
+
"linkedinbot",
|
|
50
|
+
"slackbot",
|
|
51
|
+
"discordbot",
|
|
52
|
+
"whatsapp",
|
|
53
|
+
"telegram"
|
|
54
|
+
],
|
|
55
|
+
debugHeaders: true
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
59
|
+
const SAMPLE_POST = `---
|
|
60
|
+
title: Hello World
|
|
61
|
+
excerpt: This is a sample page generated by RenderShield.
|
|
62
|
+
datePublished: ${today}
|
|
63
|
+
coverImage: /images/hello.jpg
|
|
64
|
+
slug: hello-world
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
This is **RenderShield**.
|
|
68
|
+
|
|
69
|
+
- Crawlers should see real HTML.
|
|
70
|
+
- Humans keep the SPA.
|
|
71
|
+
|
|
72
|
+
That's the whole trick.
|
|
73
|
+
`;
|
|
74
|
+
export async function cmdInit(cwd = process.cwd()) {
|
|
75
|
+
const configPath = path.join(cwd, CONFIG_NAME);
|
|
76
|
+
const contentDir = path.join(cwd, "content", "pages");
|
|
77
|
+
const samplePath = path.join(contentDir, "hello-world.md");
|
|
78
|
+
const configExists = await fs.pathExists(configPath);
|
|
79
|
+
if (!configExists) {
|
|
80
|
+
await fs.writeFile(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n", "utf8");
|
|
81
|
+
console.log(`Created ${CONFIG_NAME}`);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.log(`${CONFIG_NAME} already exists (leaving it alone)`);
|
|
85
|
+
}
|
|
86
|
+
await fs.ensureDir(contentDir);
|
|
87
|
+
const sampleExists = await fs.pathExists(samplePath);
|
|
88
|
+
if (!sampleExists) {
|
|
89
|
+
await fs.writeFile(samplePath, SAMPLE_POST, "utf8");
|
|
90
|
+
console.log(`Created sample content: content/pages/hello-world.md`);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
console.log(`Sample content already exists (leaving it alone)`);
|
|
94
|
+
}
|
|
95
|
+
console.log(`
|
|
96
|
+
Next:
|
|
97
|
+
1) Edit rendershield.config.json
|
|
98
|
+
2) Add content under content/pages/
|
|
99
|
+
3) Run: rendershield build
|
|
100
|
+
`);
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { loadConfig } from "../core/loadConfig.js";
|
|
4
|
+
function joinUrl(base, routePath) {
|
|
5
|
+
const b = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
6
|
+
const p = routePath.startsWith("/") ? routePath : `/${routePath}`;
|
|
7
|
+
return b + p;
|
|
8
|
+
}
|
|
9
|
+
async function findFirstIndexHtml(outDirAbs) {
|
|
10
|
+
const stack = [outDirAbs];
|
|
11
|
+
while (stack.length > 0) {
|
|
12
|
+
const current = stack.pop();
|
|
13
|
+
let entries;
|
|
14
|
+
try {
|
|
15
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
const full = path.join(current, entry.name);
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
stack.push(full);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (entry.isFile() && entry.name.toLowerCase() === "index.html") {
|
|
28
|
+
// Ignore index.html at the output root; prefer a routed page like
|
|
29
|
+
// <section>/<slug>/index.html (or deeper).
|
|
30
|
+
const rel = path.relative(outDirAbs, full);
|
|
31
|
+
const parts = rel.split(path.sep).filter(Boolean);
|
|
32
|
+
if (parts.length >= 2)
|
|
33
|
+
return full;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
function indexHtmlPathToRoute(outDirAbs, indexPathAbs) {
|
|
40
|
+
const rel = path.relative(outDirAbs, indexPathAbs);
|
|
41
|
+
// rel: <section>/<slug>/index.html
|
|
42
|
+
const noFile = rel.replace(/index\.html$/i, "");
|
|
43
|
+
const normalized = noFile.split(path.sep).join("/").replace(/\/+$/, "");
|
|
44
|
+
return "/" + normalized.replace(/^\/+/, "");
|
|
45
|
+
}
|
|
46
|
+
export async function cmdVerify(cwd = process.cwd()) {
|
|
47
|
+
const cfg = await loadConfig(cwd);
|
|
48
|
+
const outDirAbs = path.join(cwd, cfg.output.outDir);
|
|
49
|
+
const exists = await fs.pathExists(outDirAbs);
|
|
50
|
+
if (!exists) {
|
|
51
|
+
console.log(`
|
|
52
|
+
RenderShield verify
|
|
53
|
+
|
|
54
|
+
No prerender output directory found: ${cfg.output.outDir}/
|
|
55
|
+
|
|
56
|
+
Run:
|
|
57
|
+
node dist/cli.js build
|
|
58
|
+
`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const firstIndex = await findFirstIndexHtml(outDirAbs);
|
|
62
|
+
if (!firstIndex) {
|
|
63
|
+
console.log(`
|
|
64
|
+
RenderShield verify
|
|
65
|
+
|
|
66
|
+
No prerendered pages found inside: ${cfg.output.outDir}/
|
|
67
|
+
|
|
68
|
+
Run:
|
|
69
|
+
node dist/cli.js build
|
|
70
|
+
`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const routePath = indexHtmlPathToRoute(outDirAbs, firstIndex);
|
|
74
|
+
const url = joinUrl(cfg.site.canonicalBase, routePath);
|
|
75
|
+
console.log(`
|
|
76
|
+
RenderShield verify
|
|
77
|
+
|
|
78
|
+
Using:
|
|
79
|
+
canonicalBase: ${cfg.site.canonicalBase}
|
|
80
|
+
routePath: ${routePath}
|
|
81
|
+
output file: ${path.relative(cwd, firstIndex)}
|
|
82
|
+
|
|
83
|
+
Smoke tests:
|
|
84
|
+
|
|
85
|
+
1) Human (usually SPA shell):
|
|
86
|
+
curl -s ${url} | grep -i "<title>"
|
|
87
|
+
|
|
88
|
+
2) Bot (should see prerendered, route-specific title):
|
|
89
|
+
curl -s -H "User-Agent: Googlebot" ${url} | grep -i "<title>"
|
|
90
|
+
|
|
91
|
+
3) Debug headers (Worker must be routed + proxy ON):
|
|
92
|
+
curl -I -H "User-Agent: GPTBot" ${url}
|
|
93
|
+
|
|
94
|
+
Expected headers (if debugHeaders enabled):
|
|
95
|
+
X-Bot-Detected: true
|
|
96
|
+
X-Prerender: true
|
|
97
|
+
X-Final-Path: ${routePath.replace(/\/?$/, "/index.html")}
|
|
98
|
+
`);
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function generateRobotsTxt(cfg) {
|
|
2
|
+
const sitemapUrl = cfg.sitemap.enabled
|
|
3
|
+
? `${cfg.site.canonicalBase.replace(/\/$/, "")}${cfg.sitemap.path}`
|
|
4
|
+
: "";
|
|
5
|
+
const lines = [
|
|
6
|
+
"User-agent: *",
|
|
7
|
+
"Allow: /",
|
|
8
|
+
];
|
|
9
|
+
if (sitemapUrl) {
|
|
10
|
+
lines.push(`Sitemap: ${sitemapUrl}`);
|
|
11
|
+
}
|
|
12
|
+
return lines.join("\n") + "\n";
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=generateRobots.js.map
|