pagegraph 0.11.0 → 0.13.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/README.md +403 -148
- package/dist/audit.d.ts +2 -2
- package/dist/audit.js +4 -4
- package/dist/build-only/audit.d.ts +1 -0
- package/dist/build-only/audit.js +6 -0
- package/dist/build-only/audit.js.map +1 -0
- package/dist/build-only/claims.d.ts +1 -0
- package/dist/build-only/claims.js +6 -0
- package/dist/build-only/claims.js.map +1 -0
- package/dist/build-only/config.d.ts +1 -0
- package/dist/build-only/config.js +6 -0
- package/dist/build-only/config.js.map +1 -0
- package/dist/build-only/oxlint.d.ts +1 -0
- package/dist/build-only/oxlint.js +6 -0
- package/dist/build-only/oxlint.js.map +1 -0
- package/dist/build-only/tanstack-start.d.ts +1 -0
- package/dist/build-only/tanstack-start.js +6 -0
- package/dist/build-only/tanstack-start.js.map +1 -0
- package/dist/build-only/vite.d.ts +1 -0
- package/dist/build-only/vite.js +6 -0
- package/dist/build-only/vite.js.map +1 -0
- package/dist/{checks-nk8ZRRIA.js → checks-BS-aDXB_.js} +4 -142
- package/dist/checks-BS-aDXB_.js.map +1 -0
- package/dist/claims-Bch7Lxlo.d.ts +92 -0
- package/dist/claims-Bv7voeEV.js +431 -0
- package/dist/claims-Bv7voeEV.js.map +1 -0
- package/dist/claims.d.ts +94 -0
- package/dist/claims.js +2 -0
- package/dist/cli.js +10368 -8485
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +4 -83
- package/dist/config.js +3 -122
- package/dist/config.js.map +1 -1
- package/dist/{content-CB_kHmJ3.js → content-CHd0X4s6.js} +3 -2
- package/dist/{content-CB_kHmJ3.js.map → content-CHd0X4s6.js.map} +1 -1
- package/dist/{content-DVL2iyGU.d.ts → content-DgNr7apS.d.ts} +5 -130
- package/dist/declare-D5o7hwmv.js +9 -0
- package/dist/declare-D5o7hwmv.js.map +1 -0
- package/dist/document-CztEu3Vd.d.ts +109 -0
- package/dist/document-Dvemcv7V.js +379 -0
- package/dist/document-Dvemcv7V.js.map +1 -0
- package/dist/facts-1OKclPOk.js +115 -0
- package/dist/facts-1OKclPOk.js.map +1 -0
- package/dist/graph-D1PIndsC.d.ts +12238 -0
- package/dist/graph-DMNOLXLu.js +172 -0
- package/dist/graph-DMNOLXLu.js.map +1 -0
- package/dist/index-C5w9ripz.d.ts +204 -0
- package/dist/index.d.ts +6 -196
- package/dist/index.js +55 -134
- package/dist/index.js.map +1 -1
- package/dist/inline-BbS93GAT.js +682 -0
- package/dist/inline-BbS93GAT.js.map +1 -0
- package/dist/llms-Dhr_5u2O.js +19 -0
- package/dist/llms-Dhr_5u2O.js.map +1 -0
- package/dist/markdown-C6dRE9zD.js +168 -0
- package/dist/markdown-C6dRE9zD.js.map +1 -0
- package/dist/markdown-path-lJB9BAVZ.js +6 -0
- package/dist/markdown-path-lJB9BAVZ.js.map +1 -0
- package/dist/message-CuBjgZ74.js +12 -0
- package/dist/message-CuBjgZ74.js.map +1 -0
- package/dist/oxlint.d.ts +58 -0
- package/dist/oxlint.js +202 -0
- package/dist/oxlint.js.map +1 -0
- package/dist/page-heads-BfjWQvau.d.ts +108 -0
- package/dist/page-heads-BgbaIjj9.js +25 -0
- package/dist/page-heads-BgbaIjj9.js.map +1 -0
- package/dist/projections-Bk-qOOsR.d.ts +78 -0
- package/dist/projections-D0oOFKiY.js +142 -0
- package/dist/projections-D0oOFKiY.js.map +1 -0
- package/dist/react.d.ts +27 -12051
- package/dist/react.js +14 -4
- package/dist/react.js.map +1 -1
- package/dist/route-config-35ZEzkNC.js +308 -0
- package/dist/route-config-35ZEzkNC.js.map +1 -0
- package/dist/route-config-wneg6ktn.d.ts +29 -0
- package/dist/tanstack-start/index.d.ts +56 -0
- package/dist/tanstack-start/index.js +717 -0
- package/dist/tanstack-start/index.js.map +1 -0
- package/dist/tanstack-start/markdown.d.ts +12 -0
- package/dist/tanstack-start/markdown.js +149 -0
- package/dist/tanstack-start/markdown.js.map +1 -0
- package/dist/tanstack-start/prerender-worker.d.ts +2 -0
- package/dist/tanstack-start/prerender-worker.js +7 -0
- package/dist/tanstack-start/prerender-worker.js.map +1 -0
- package/dist/tanstack-start/react.d.ts +7 -0
- package/dist/tanstack-start/react.js +12 -0
- package/dist/tanstack-start/react.js.map +1 -0
- package/dist/tanstack-start/server.d.ts +20 -0
- package/dist/tanstack-start/server.js +77 -0
- package/dist/tanstack-start/server.js.map +1 -0
- package/dist/vite-graph-loader-CsGiYNWE.js +105 -0
- package/dist/vite-graph-loader-CsGiYNWE.js.map +1 -0
- package/dist/vite-graph-loader-DZCwejuJ.d.ts +133 -0
- package/dist/vite.d.ts +2 -28
- package/dist/vite.js +1 -307
- package/dist/wire-B4RQMc4e.js +26 -0
- package/dist/wire-B4RQMc4e.js.map +1 -0
- package/package.json +70 -7
- package/dist/checks-nk8ZRRIA.js.map +0 -1
- package/dist/vite.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
# pagegraph
|
|
2
2
|
|
|
3
|
-
Route-declared SEO for TanStack Router. Each route declares its SEO
|
|
4
|
-
`staticData.seo` — the sitemap, `robots.txt`,
|
|
5
|
-
the CI check all derive from that one
|
|
6
|
-
so nothing drifts. A public page that
|
|
3
|
+
Route-declared SEO for TanStack Start and TanStack Router. Each route declares its SEO
|
|
4
|
+
policy and head once, in `staticData.seo` — the `<head>`, sitemap, `robots.txt`,
|
|
5
|
+
breadcrumbs, JSON-LD, cross-links, and the CI check all derive from that one
|
|
6
|
+
declaration. There is no second place to update, so nothing drifts. A public page that
|
|
7
|
+
ships without a declaration **fails the build**.
|
|
7
8
|
|
|
8
9
|
```mermaid
|
|
9
10
|
flowchart LR
|
|
10
11
|
subgraph declare["declare once"]
|
|
11
|
-
route["staticData.seo<br
|
|
12
|
+
route["staticData.seo (+ head)<br/>head: seo.head"]
|
|
12
13
|
end
|
|
13
|
-
route --> seograph["SeoGraph<br/>(
|
|
14
|
+
route --> seograph["SeoGraph<br/>(built by the Vite plugin)"]
|
|
14
15
|
seograph --> sitemap["sitemap.xml"]
|
|
15
16
|
seograph --> robots["robots.txt"]
|
|
16
17
|
seograph --> check["pagegraph check · CI gate"]
|
|
17
18
|
route --> head["<head> meta · canonical<br/>Breadcrumbs · JSON-LD"]
|
|
18
|
-
gate["
|
|
19
|
+
gate["pagegraph() (vite)"] -. "fails undeclared pages" .-> route
|
|
19
20
|
```
|
|
20
21
|
|
|
21
22
|
## Install
|
|
@@ -25,17 +26,201 @@ bun add pagegraph
|
|
|
25
26
|
bun add -D lighthouse # only for `pagegraph audit` performance evidence
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
| ---------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------- |
|
|
30
|
-
| `pagegraph` | `buildSeoGraph`, `renderSitemap`, `renderRobots`, `contentSignal`, `checkGraph`, `checkRenderedCoverage`, `decodeRenderedEdgeArtifact`, `inspectHtml` | — |
|
|
31
|
-
| `pagegraph/react` | `createSeo` → `seoHead`, `Breadcrumbs`, JSON-LD generators | `react`, `@tanstack/react-router` |
|
|
32
|
-
| `pagegraph/vite` | `seoRouteConfig` coverage gate | `vite` |
|
|
33
|
-
| `pagegraph/config` | `defineSeoConfig`, `viteGraphLoader`, `selectPageHeadNodes`, `pageHeads`, `loadPageHeads` | `vite` |
|
|
34
|
-
| `pagegraph/audit` | Audit services, scanner protocol, rules, and report schemas | `effect` |
|
|
35
|
-
| `pagegraph` bin | CLI over the same graph | bundled — runs on Bun |
|
|
29
|
+
## Authored pages
|
|
36
30
|
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
Keep authored text beside its React layout. `DocumentProvider` records only its page body when
|
|
32
|
+
supplied a collector; ordinary rendering preserves the layout. `Title` assigns a heading to the
|
|
33
|
+
nearest section or item, `Fact` resolves shared values, and `Visual` describes content for the
|
|
34
|
+
markdown document without rendering its children during capture.
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
38
|
+
import { defineFacts, fact, documentMarkdown } from "pagegraph";
|
|
39
|
+
import {
|
|
40
|
+
DocumentProvider, T, Title, Section, Fact, createCollector, finishDocument,
|
|
41
|
+
} from "pagegraph/react";
|
|
42
|
+
|
|
43
|
+
const facts = defineFacts({ attempts: fact.number(8) });
|
|
44
|
+
declare module "pagegraph" {
|
|
45
|
+
interface Register { facts: typeof facts }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const collector = createCollector({ path: "/email", site: "https://example.com", facts });
|
|
49
|
+
const page = (
|
|
50
|
+
<DocumentProvider collector={collector}>
|
|
51
|
+
<Section kind="prose" id="intro">
|
|
52
|
+
<h1><Title>Email delivery</Title></h1>
|
|
53
|
+
<p><T>Retry up to <Fact id="attempts" /> times.</T></p>
|
|
54
|
+
</Section>
|
|
55
|
+
</DocumentProvider>
|
|
56
|
+
);
|
|
57
|
+
// Your SSR renderer supplies completed HTML, whose markers determine document order.
|
|
58
|
+
const html = renderToStaticMarkup(page);
|
|
59
|
+
const document = finishDocument(collector, {
|
|
60
|
+
title: "Email delivery", description: "Reliable delivery with retries.",
|
|
61
|
+
}, html);
|
|
62
|
+
const markdown = documentMarkdown(document, "https://example.com");
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Use `defineSectionKind` for custom formatting; its markdown writer provides `text`, `heading`,
|
|
66
|
+
and `table`:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { defineSectionKind } from "pagegraph";
|
|
70
|
+
const comparison = defineSectionKind({
|
|
71
|
+
kind: "comparison",
|
|
72
|
+
markdown: (section, md) => md.heading(2, section.title)
|
|
73
|
+
+ md.table(section.items.map((item) => item.cells)),
|
|
74
|
+
}); // pass kind={comparison} to Section
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`Section.Item.Link` records a card's destination, while `CaptureAnchor` is the
|
|
78
|
+
public anchor boundary for router `createLink` integrations. `ForAgents` and `ForHumans` mark
|
|
79
|
+
audience-specific text; markdown excludes human-only content. `messageText` extracts text or
|
|
80
|
+
inline markdown for head metadata. The root entry also provides `hashDocument`,
|
|
81
|
+
`createMarkdownLock`, and pure `llmsMarkdown`/`llmsSection` string builders.
|
|
82
|
+
|
|
83
|
+
## Rendered markdown with TanStack Start
|
|
84
|
+
|
|
85
|
+
Declare rendered pages on the route that owns `DocumentProvider`. Collection instances inherit
|
|
86
|
+
these fields from their parameter route; the parameter template itself is never captured.
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
90
|
+
import { DocumentProvider, Section, T, Title } from "pagegraph/react";
|
|
91
|
+
import { Link } from "pagegraph/tanstack-start/react";
|
|
92
|
+
|
|
93
|
+
export const Route = createFileRoute("/pricing")({
|
|
94
|
+
staticData: {
|
|
95
|
+
seo: { kind: "page", head: { title: "Pricing", description: "Plans for your team." } },
|
|
96
|
+
markdown: "rendered",
|
|
97
|
+
llms: "Product",
|
|
98
|
+
},
|
|
99
|
+
head: () => ({ meta: [{ title: "Pricing" }, { name: "description", content: "Plans for your team." }] }),
|
|
100
|
+
component: () => (
|
|
101
|
+
<DocumentProvider>
|
|
102
|
+
<Section kind="prose" id="plans">
|
|
103
|
+
<h1><Title>Plans for your team</Title></h1>
|
|
104
|
+
<Link to="/"><T>Back home</T></Link>
|
|
105
|
+
</Section>
|
|
106
|
+
</DocumentProvider>
|
|
107
|
+
),
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`markdown: "source"` identifies pages whose Markdown is supplied by the application; pagegraph
|
|
112
|
+
captures only `"rendered"` pages. `llms` sets an optional llms.txt group. Use the capture-aware
|
|
113
|
+
`Link` for authored links: it uses TanStack's ordinary Link outside a capture and records resolved
|
|
114
|
+
anchor destinations during capture. The plugin adds `source=` locations to authored JSX imported
|
|
115
|
+
from `pagegraph/react`. In development, `DocumentProvider` rejects a current route lacking
|
|
116
|
+
`staticData.markdown: "rendered"`.
|
|
117
|
+
|
|
118
|
+
Configure the plugin and pass its fixed capture page to Start:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { pagegraph } from "pagegraph/tanstack-start";
|
|
122
|
+
|
|
123
|
+
const graph = pagegraph({
|
|
124
|
+
origin: "https://preview.example.com", // this deployment's SEO origin
|
|
125
|
+
collections: "src/collections.ts",
|
|
126
|
+
markdown: {
|
|
127
|
+
origin: "https://example.com", // canonical document and twin links
|
|
128
|
+
serverEntry: "dist/server/index.js", // default; relative to the Vite root
|
|
129
|
+
},
|
|
130
|
+
facts: "src/facts.ts", // exports `facts`; evaluated in the pagegraph environment
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// In your Vite plugins:
|
|
134
|
+
// tanstackStart({
|
|
135
|
+
// prerender: { enabled: true, autoStaticPathsDiscovery: false, crawlLinks: false, failOnError: true },
|
|
136
|
+
// pages: [...graph.prerenderPages],
|
|
137
|
+
// }), graph
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
For Cloudflare, wire the non-deployed prerender Worker to the compiled Start server. Add this to
|
|
141
|
+
`cloudflare()` alongside the production Worker's configuration:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
experimental: {
|
|
145
|
+
prerenderWorker: {
|
|
146
|
+
config: {
|
|
147
|
+
name: "app-markdown-prerender",
|
|
148
|
+
main: "pagegraph/tanstack-start/prerender-worker",
|
|
149
|
+
compatibility_date: "2026-09-20",
|
|
150
|
+
compatibility_flags: ["nodejs_compat"],
|
|
151
|
+
vars: { TSS_PRERENDERING: "true" },
|
|
152
|
+
assets: { binding: "ASSETS", run_worker_first: true },
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Call the capture adapter before your normal Start handler, forwarding the same request options:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import handler from "@tanstack/react-start/server-entry";
|
|
162
|
+
import { markdownRequest } from "pagegraph/tanstack-start/markdown";
|
|
163
|
+
|
|
164
|
+
export default {
|
|
165
|
+
async fetch(request: Request) {
|
|
166
|
+
return await markdownRequest(request) ?? handler.fetch(request);
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Only dev and the prerender Worker capture documents. `GET` and `HEAD` on
|
|
172
|
+
`/__pagegraph/markdown.json` capture all rendered pages; `/pricing.md` and
|
|
173
|
+
`/pricing.document.json` capture one page in dev. Ordinary requests return `null`. In production,
|
|
174
|
+
private capture paths return 404 and Markdown twins fall through to your static asset server.
|
|
175
|
+
`markdownPagePaths()` exposes the concrete rendered page paths.
|
|
176
|
+
|
|
177
|
+
After prerender, the plugin writes `dist/client/pricing.md` (`/` becomes `index.md`),
|
|
178
|
+
`.pagegraph/documents/pricing.json`, and `.pagegraph/heads.json`, and removes the emitted private
|
|
179
|
+
capture response. Stale valid documents at their derived paths are pruned; unrelated files are
|
|
180
|
+
preserved. Ignore the private generated documents and heads in Git. Claims replay is reserved for
|
|
181
|
+
a separate integration.
|
|
182
|
+
|
|
183
|
+
Serve graph-derived llms.txt without rendering pages:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
187
|
+
import { llmsTxt } from "pagegraph/tanstack-start/server";
|
|
188
|
+
|
|
189
|
+
export const Route = createFileRoute("/llms.txt")({
|
|
190
|
+
server: { handlers: { GET: llmsTxt({ sections: ["## Docs\n\nRead our documentation."] }) } },
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`llmsSection()` returns the generated section for custom composition. It includes rendered twins
|
|
195
|
+
and explicitly grouped pages, using graph titles and descriptions and `markdown.origin`.
|
|
196
|
+
|
|
197
|
+
## What runs where
|
|
198
|
+
|
|
199
|
+
Every entry belongs to one place. Runtime entries use portable graph and document code with
|
|
200
|
+
the framework peers listed below; the capture adapter runs on a Start server. Build entries
|
|
201
|
+
load your app through Vite, parse routes natively, or drive Node I/O.
|
|
202
|
+
|
|
203
|
+
| Entry | Runs in | Exports | Peers |
|
|
204
|
+
| ------------------ | ----------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------- |
|
|
205
|
+
| `pagegraph` | runtime (Worker, SSR, browser) | `buildSeoGraph`, `contentCollection`, `renderSitemap`, `renderRobots`, `pageHeads`, `graphToJson`/`graphFromJson`, checks, `inspectHtml`, document types, facts, markdown and locks | — |
|
|
206
|
+
| `pagegraph/react` | runtime | `createSeo` → `seo.head`, `seoHead`, `Breadcrumbs`, JSON-LD generators, authored page primitives | `react`, `@tanstack/react-router` |
|
|
207
|
+
| `pagegraph/tanstack-start/server` | runtime (Start server routes) | `robotsTxt`, `sitemapXml`, `llmsTxt`, `llmsSection`, `seoGraph`, `seoSite` | the `pagegraph()` plugin |
|
|
208
|
+
| `pagegraph/tanstack-start/markdown` | runtime (Start server) | `markdownRequest`, `markdownPagePaths` | `react`, `react-dom`, `@tanstack/react-start`, `@tanstack/react-router` |
|
|
209
|
+
| `pagegraph/tanstack-start/react` | runtime | capture-aware `Link` | `react`, `@tanstack/react-router` |
|
|
210
|
+
| `pagegraph/tanstack-start/prerender-worker` | non-deployed prerender Worker | compiled Start server | the `pagegraph()` plugin |
|
|
211
|
+
| `pagegraph/tanstack-start` | build (`vite.config.ts`, `pagegraph.config.ts`) | `pagegraph()` Vite plugin, `tanstackStartGraph`, `evaluateAppGraph` | `vite`, `@tanstack/router-generator`, `effect` |
|
|
212
|
+
| `pagegraph/vite` | build (`vite.config.ts`) | `seoRouteConfig` coverage gate | `vite`, `@tanstack/router-generator` |
|
|
213
|
+
| `pagegraph/config` | build (`pagegraph.config.ts`, CLI) | `defineSeoConfig`, `viteGraphLoader`, `loadPageHeads` | `vite` |
|
|
214
|
+
| `pagegraph/claims` | build / CLI (Node or Bun) | `claimsInput`, `claimsFamily`, document resolution, committed claims checks | `effect` |
|
|
215
|
+
| `pagegraph/audit` | build / CLI (Node or Bun) | Audit services, scanner protocol, rules, and report schemas | `effect` |
|
|
216
|
+
| `pagegraph/oxlint` | build (oxlint) | `pagegraph` plugin: `no-bare-text`, `t-children` | — |
|
|
217
|
+
| `pagegraph` bin | CLI (Bun) | CLI over the same graph | bundled |
|
|
218
|
+
|
|
219
|
+
Build entries resolve to a stub under the `workerd`, `worker`, and `browser` export conditions.
|
|
220
|
+
A Worker or browser bundle that reaches one fails at import with an error naming the entry,
|
|
221
|
+
instead of crashing on a native binding when a request first runs the code. The runtime entries
|
|
222
|
+
have **zero runtime dependencies** beyond their listed peers; a test pins each entry's import
|
|
223
|
+
graph.
|
|
39
224
|
|
|
40
225
|
The CLI **bundles PageGraph's Effect and TypeSafe provider runtime**, so it does not depend on the
|
|
41
226
|
app's Effect version; it runs on [Bun](https://bun.sh) (`bunx pagegraph`). Agentic workflows load the
|
|
@@ -46,8 +231,9 @@ Effect and Drizzle versions. Executor plugins must use OpenCode's Effect version
|
|
|
46
231
|
Bun and network access to npm are required for the first install. The pinned OpenCode client's
|
|
47
232
|
HTTP error handling retains the method, path, status, and response body for diagnostics;
|
|
48
233
|
undeclared error bodies are capped at 16 KiB.
|
|
49
|
-
Discovery-only research gets one continuation to complete provider evidence
|
|
50
|
-
`
|
|
234
|
+
Discovery-only research gets one continuation to complete provider evidence. Every workflow failure
|
|
235
|
+
after run creation writes `failure.json` in the run directory, with the stage, session ID when available,
|
|
236
|
+
transcript, and cause chain. `vite` stays a peer — graph commands
|
|
51
237
|
load your app through Vite at runtime — and `lighthouse` is only needed by `pagegraph audit`.
|
|
52
238
|
Importing `pagegraph/audit` programmatically needs `effect@^4.0.0`. The Effect and TypeSafe
|
|
53
239
|
peers remain optional for other library entry points and the bundled CLI.
|
|
@@ -63,7 +249,7 @@ pagegraph skills get core
|
|
|
63
249
|
|
|
64
250
|
The skill is maintained at [skill-data/core/SKILL.md](skill-data/core/SKILL.md) and compiled into the CLI, so the instructions match the installed Pagegraph version. `skills get core` prints Markdown to stdout; `--json` returns a structured payload.
|
|
65
251
|
|
|
66
|
-
## Quick start
|
|
252
|
+
## Quick start (TanStack Start)
|
|
67
253
|
|
|
68
254
|
**1. Bind your site identity once.** Route files never see an origin or a brand name.
|
|
69
255
|
|
|
@@ -71,7 +257,7 @@ The skill is maintained at [skill-data/core/SKILL.md](skill-data/core/SKILL.md)
|
|
|
71
257
|
// lib/seo.ts
|
|
72
258
|
import { createSeo } from "pagegraph/react";
|
|
73
259
|
|
|
74
|
-
|
|
260
|
+
const site = createSeo({
|
|
75
261
|
origin: "https://example.com",
|
|
76
262
|
site: {
|
|
77
263
|
name: "Example",
|
|
@@ -85,19 +271,17 @@ export const { seoHead } = createSeo({
|
|
|
85
271
|
description: "What the company does.",
|
|
86
272
|
sameAs: ["https://github.com/example"],
|
|
87
273
|
contactPoint: { contactType: "support", email: "hi@example.com" },
|
|
88
|
-
address: {
|
|
89
|
-
streetAddress: "123 Example Street",
|
|
90
|
-
addressLocality: "Example City",
|
|
91
|
-
addressRegion: "CA",
|
|
92
|
-
postalCode: "94105",
|
|
93
|
-
addressCountry: "US",
|
|
94
|
-
},
|
|
95
274
|
},
|
|
96
275
|
website: { searchPath: "/docs?q={search_term_string}" },
|
|
97
276
|
});
|
|
277
|
+
|
|
278
|
+
export const seo = site; // seo.head for static routes
|
|
279
|
+
export const { seoHead } = site;
|
|
98
280
|
```
|
|
99
281
|
|
|
100
|
-
**2. Declare
|
|
282
|
+
**2. Declare each page once.** `staticData.seo` holds the crawl policy and the head
|
|
283
|
+
together; `seo.head` renders it. The graph and the checks read the same
|
|
284
|
+
`staticData.seo.head` — the page and the graph cannot disagree.
|
|
101
285
|
|
|
102
286
|
```tsx
|
|
103
287
|
export const Route = createFileRoute("/pricing")({
|
|
@@ -109,124 +293,111 @@ export const Route = createFileRoute("/pricing")({
|
|
|
109
293
|
related: ["/features", "/docs"],
|
|
110
294
|
link: { title: "Pricing", description: "Simple volume pricing." },
|
|
111
295
|
modifiedAt: "2026-09-29", // sitemap <lastmod> and the freshness report
|
|
296
|
+
head: {
|
|
297
|
+
title: "Pricing — Example",
|
|
298
|
+
description: "Simple volume pricing.",
|
|
299
|
+
faqs: pricingFaqs,
|
|
300
|
+
},
|
|
112
301
|
},
|
|
113
302
|
},
|
|
114
|
-
head:
|
|
115
|
-
|
|
116
|
-
title: "Pricing — Example",
|
|
117
|
-
description: "Simple volume pricing.",
|
|
118
|
-
}),
|
|
303
|
+
head: seo.head,
|
|
304
|
+
component: PricingPage,
|
|
119
305
|
});
|
|
120
306
|
```
|
|
121
307
|
|
|
122
|
-
`
|
|
308
|
+
`seo.head` returns the `meta` + canonical `links` TanStack renders into `<head>` —
|
|
123
309
|
title, description, og/twitter cards, robots, and the JSON-LD each page warrants
|
|
124
|
-
(BreadcrumbList always; Article, FAQPage, Service, ItemList when the
|
|
125
|
-
|
|
310
|
+
(BreadcrumbList always; Article, FAQPage, Service, ItemList when the head declares
|
|
311
|
+
them). A route that renders from `loaderData` calls `seoHead(ctx, instance)` in its
|
|
312
|
+
own `head()` and declares a `titleTemplate` (`"%s | Example Blog"`): `seoHead` applies it,
|
|
313
|
+
and the graph applies it to the route's collection pages.
|
|
126
314
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
`schema-dts` entity, link it to the plugin's stable site identities, and compose one
|
|
131
|
-
site graph:
|
|
315
|
+
**3. Register the plugin.** It builds the graph once per build (and on change in dev)
|
|
316
|
+
inside your app's Vite pipeline — path aliases, `define`s, and content plugins such as
|
|
317
|
+
Fumadocs apply, with no stubs — and ships it to the server runtime as data.
|
|
132
318
|
|
|
133
319
|
```ts
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
defineJsonLd,
|
|
137
|
-
extendJsonLd,
|
|
138
|
-
jsonLdRef,
|
|
139
|
-
} from "pagegraph/react";
|
|
320
|
+
// vite.config.ts
|
|
321
|
+
import { pagegraph } from "pagegraph/tanstack-start";
|
|
140
322
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
provider: jsonLdRef(ids.organization),
|
|
152
|
-
}),
|
|
153
|
-
],
|
|
154
|
-
transform: (entry) => {
|
|
155
|
-
if (entry.kind === "organization") {
|
|
156
|
-
return extendJsonLd(entry.document, {
|
|
157
|
-
slogan: "Ship with confidence.",
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
return entry.document;
|
|
161
|
-
},
|
|
323
|
+
pagegraph({
|
|
324
|
+
origin: "https://example.com",
|
|
325
|
+
indexable: process.env.DEPLOY_ENV === "production", // previews disallow everything
|
|
326
|
+
robots: { contentSignal: "search=yes, ai-input=yes, ai-train=yes" },
|
|
327
|
+
collections: "src/lib/seo/collections.ts",
|
|
328
|
+
routeConfig: {
|
|
329
|
+
outputPath: fileURLToPath(new URL("./src/lib/route-config.ts", import.meta.url)),
|
|
330
|
+
publicGroups: ["(marketing)", "(docs)"],
|
|
331
|
+
enforceCoverageIn: ["(marketing)", "(docs)"],
|
|
332
|
+
alwaysDisallow: ["/dashboard", "/api"],
|
|
162
333
|
},
|
|
163
334
|
});
|
|
164
|
-
|
|
165
|
-
const siteGraph = seo.generateSiteGraphSchema();
|
|
166
335
|
```
|
|
167
336
|
|
|
168
|
-
`generateSiteGraphSchema()` includes Organization, WebSite, and configured site
|
|
169
|
-
entities in one `@graph`. Organization and WebSite use stable `#organization` and
|
|
170
|
-
`#website` IDs; articles and services provided by the site reference the same
|
|
171
|
-
Organization. Existing individual generators remain available and are not transformed.
|
|
172
|
-
|
|
173
|
-
Add page-specific entities through `seoHead`:
|
|
174
|
-
|
|
175
337
|
```ts
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
338
|
+
// src/lib/seo/collections.ts — evaluated in the app's Vite pipeline
|
|
339
|
+
import { contentCollection } from "pagegraph";
|
|
340
|
+
import { blogSource } from "../blog-source"; // e.g. a Fumadocs loader()
|
|
341
|
+
|
|
342
|
+
export const collections = () => [
|
|
343
|
+
contentCollection({
|
|
344
|
+
route: "/blog/$slug",
|
|
345
|
+
source: "blog",
|
|
346
|
+
pages: blogSource.getPages(),
|
|
347
|
+
entry: (page) => ({
|
|
348
|
+
title: page.data.title,
|
|
349
|
+
description: page.data.description,
|
|
350
|
+
publishedAt: page.data.date,
|
|
351
|
+
related: page.data.related,
|
|
352
|
+
}),
|
|
353
|
+
}),
|
|
354
|
+
];
|
|
188
355
|
```
|
|
189
356
|
|
|
190
|
-
The
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
357
|
+
The plugin evaluates every route file that declares `staticData` — never the generated
|
|
358
|
+
route tree — in a Node environment named `pagegraph`. A route that imports a module
|
|
359
|
+
only the server runtime provides (`cloudflare:workers`) fails with its file name; add an
|
|
360
|
+
app-only route to `exclude` (route-file globs).
|
|
194
361
|
|
|
195
|
-
**
|
|
362
|
+
**4. Serve robots.txt and sitemap.xml.** One line each; the output depends on the
|
|
363
|
+
deployment's origin and indexability, so they stay routes.
|
|
196
364
|
|
|
197
365
|
```ts
|
|
198
|
-
//
|
|
199
|
-
import {
|
|
366
|
+
// src/routes/robots[.]txt.ts — sitemap[.]xml.ts is symmetric with sitemapXml
|
|
367
|
+
import { robotsTxt } from "pagegraph/tanstack-start/server";
|
|
200
368
|
|
|
201
|
-
export const
|
|
202
|
-
buildSeoGraph({ routeTree, collections: [blogCollection] });
|
|
369
|
+
export const Route = createFileRoute("/robots.txt")({ server: { handlers: { GET: robotsTxt } } });
|
|
203
370
|
```
|
|
204
371
|
|
|
372
|
+
**5. Gate CI.** The CLI reads the same graph through `pagegraph.config.ts`:
|
|
373
|
+
|
|
205
374
|
```ts
|
|
206
|
-
//
|
|
207
|
-
import {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
indexable: true,
|
|
213
|
-
disallow: routeConfig.robotsExclusions,
|
|
214
|
-
contentSignal: "search=yes, ai-input=yes, ai-train=yes",
|
|
375
|
+
// pagegraph.config.ts
|
|
376
|
+
import { defineSeoConfig } from "pagegraph/config";
|
|
377
|
+
import { tanstackStartGraph } from "pagegraph/tanstack-start";
|
|
378
|
+
|
|
379
|
+
export default defineSeoConfig({
|
|
380
|
+
loadGraph: tanstackStartGraph({ root: import.meta.dirname }),
|
|
215
381
|
});
|
|
216
382
|
```
|
|
217
383
|
|
|
384
|
+
```bash
|
|
385
|
+
pagegraph check
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Without TanStack Start
|
|
389
|
+
|
|
390
|
+
`buildSeoGraph({ routeTree, collections })` builds the same graph from any TanStack
|
|
391
|
+
Router tree; render it with `renderSitemap(graph, { origin, indexable })` and
|
|
392
|
+
`renderRobots(graph, { origin, indexable, disallow, contentSignal })`, and point
|
|
393
|
+
`pagegraph.config.ts` at it with `viteGraphLoader` (see [CLI](#cli)).
|
|
394
|
+
|
|
218
395
|
`renderRobots` does not invent a Content-Signal. Pass `contentSignal` for
|
|
219
396
|
the value (the plugin prefixes `Content-Signal: `), `directives` for extra
|
|
220
397
|
group lines, and `transform` if you need to wrap or replace the whole file.
|
|
221
398
|
Preview hosts (`indexable: false`) drop `contentSignal` and `directives`;
|
|
222
399
|
`transform` still runs.
|
|
223
400
|
|
|
224
|
-
**4. Gate CI.** The same graph, the same rules, exit 1 on structural violations.
|
|
225
|
-
|
|
226
|
-
```bash
|
|
227
|
-
pagegraph check
|
|
228
|
-
```
|
|
229
|
-
|
|
230
401
|
## Agentic workflows
|
|
231
402
|
|
|
232
403
|
PageGraph can combine the deterministic route graph with a project-owned embedded OpenCode agent,
|
|
@@ -241,7 +412,7 @@ The generated `.pagegraph/opencode` directory contains one `seo` agent and custo
|
|
|
241
412
|
the plugin own model and Executor authentication. PageGraph does not copy credentials or assume
|
|
242
413
|
fixed provider-tool addresses.
|
|
243
414
|
|
|
244
|
-
Enable workflows in `
|
|
415
|
+
Enable workflows in `pagegraph.config.ts`:
|
|
245
416
|
|
|
246
417
|
```ts
|
|
247
418
|
export default defineSeoConfig({
|
|
@@ -294,6 +465,11 @@ After research is validated, the run directory contains `research.json`: an inco
|
|
|
294
465
|
with the collected evidence, decision inputs, and OpenCode transcript. A downstream failure reports
|
|
295
466
|
its path so the evidence remains available for diagnosis. Only a completed workflow writes
|
|
296
467
|
`run.json` and `summary.md`; a research checkpoint is not a final recommendation.
|
|
468
|
+
Model JSON drops null properties and array elements before validation. Schema validation collects all
|
|
469
|
+
issues for one read-only repair turn. Every error after run creation writes `failure.json` in that
|
|
470
|
+
run directory with the workflow, stage, available OpenCode session ID, transcript, and full cause
|
|
471
|
+
chain. The error reports the artifact path and a next step; a failed artifact write preserves the
|
|
472
|
+
original error with a note.
|
|
297
473
|
|
|
298
474
|
| Command | Result |
|
|
299
475
|
| --- | --- |
|
|
@@ -382,9 +558,10 @@ route's date is never inherited by its instances. Undated pages emit no
|
|
|
382
558
|
|
|
383
559
|
## Coverage gate (Vite)
|
|
384
560
|
|
|
385
|
-
`
|
|
386
|
-
|
|
387
|
-
|
|
561
|
+
`pagegraph({ routeConfig })` — or `seoRouteConfig` from `pagegraph/vite` in a Router-only
|
|
562
|
+
app — parses the route tree with `@tanstack/router-generator` (the same parser as the
|
|
563
|
+
router) and fails `vite build` when a page in an enforced group has neither `staticData`
|
|
564
|
+
nor `head`:
|
|
388
565
|
|
|
389
566
|
```ts
|
|
390
567
|
// vite.config.ts
|
|
@@ -399,53 +576,128 @@ seoRouteConfig({
|
|
|
399
576
|
```
|
|
400
577
|
|
|
401
578
|
It also derives a small config module from the route tree: `robotsExclusions`
|
|
402
|
-
(
|
|
403
|
-
not hand out as tenant/org slugs).
|
|
579
|
+
(the plugin adds them to robots.txt; a Router-only app feeds them to `renderRobots`) and
|
|
580
|
+
`reservedSegments` (top-level segments an app must not hand out as tenant/org slugs).
|
|
581
|
+
|
|
582
|
+
## Claims gate
|
|
583
|
+
|
|
584
|
+
Declare probability rules against `claimsInput` from the build-only `pagegraph/claims` entry.
|
|
585
|
+
Each rule asks whether the section violates your policy:
|
|
586
|
+
|
|
587
|
+
```ts
|
|
588
|
+
import { Decision } from "effect/ai";
|
|
589
|
+
import { claimsInput } from "pagegraph/claims";
|
|
590
|
+
import { pagegraph } from "pagegraph/tanstack-start";
|
|
591
|
+
|
|
592
|
+
const rules = Decision.make({
|
|
593
|
+
input: claimsInput,
|
|
594
|
+
decisions: {
|
|
595
|
+
unsupportedPromise: Decision.probability({
|
|
596
|
+
instructions: "Does this section promise a capability unsupported by its facts or evidence?",
|
|
597
|
+
}),
|
|
598
|
+
},
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
const graph = pagegraph({
|
|
602
|
+
origin: "https://example.com",
|
|
603
|
+
markdown: { origin: "https://example.com" },
|
|
604
|
+
facts: "src/facts.ts",
|
|
605
|
+
claims: { rules, model: "typesafe/jev", cutoff: 0.8 },
|
|
606
|
+
});
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
Pass `graph.prerenderPages` to Start as shown in the rendered Markdown setup. The build persists
|
|
610
|
+
captured documents and graph heads, then replays committed answers without a model or credentials.
|
|
611
|
+
Missing answers and violations fail the build and name the page, section, and rule.
|
|
612
|
+
|
|
613
|
+
Run `pagegraph claims check` after changing prose, facts, context, or rules, then review and commit
|
|
614
|
+
`.pagegraph/decisions/claims/`. The cache key includes the rule definition fingerprint, model, and
|
|
615
|
+
input hash. Cached answers are reused; `--refresh` asks again and replaces them. The command reports
|
|
616
|
+
cached and asked counts and exits nonzero on violations. Set `TYPESAFE_API_KEY` when answers need
|
|
617
|
+
asking. `pagegraph check` also replays configured claims without model calls.
|
|
618
|
+
|
|
619
|
+
```sh
|
|
620
|
+
pagegraph claims check
|
|
621
|
+
pagegraph claims check --refresh
|
|
622
|
+
pagegraph claims check --dev http://localhost:3000
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
`claims.context` supplies policy text to every input. Inputs include the section's authored
|
|
626
|
+
Markdown, messages, all code-owned facts, and other sections marked as evidence. Page metadata is
|
|
627
|
+
judged as a `head` section, including graph pages outside rendered capture. `excludeHeads` accepts
|
|
628
|
+
path globs to omit metadata while keeping captured sections in the gate. The default cutoff is
|
|
629
|
+
`0.8` only when omitted; any probability at or above the configured cutoff is a violation, and
|
|
630
|
+
values below it pass. Changing the cutoff reuses answers and recalculates verdicts.
|
|
631
|
+
|
|
632
|
+
## Markdown CLI
|
|
633
|
+
|
|
634
|
+
Inspect captured documents, search page metadata and sections, and maintain a reviewable lock:
|
|
635
|
+
|
|
636
|
+
```sh
|
|
637
|
+
pagegraph markdown show /pricing
|
|
638
|
+
pagegraph markdown find email
|
|
639
|
+
pagegraph markdown lock
|
|
640
|
+
pagegraph markdown lock --check
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
`show` prints the page's Markdown twin. `find` reports matching pages and sections.
|
|
644
|
+
`lock` writes `.pagegraph/markdown.lock.json` from document and message hashes, including
|
|
645
|
+
head-only pages; `--check` exits nonzero if the lock is missing or differs. These commands use
|
|
646
|
+
`.pagegraph/documents/` and `.pagegraph/heads.json` from the build. Use `--dev <origin>` to inspect
|
|
647
|
+
the development bundle instead. `--json` emits structured output using the CLI's usual conventions.
|
|
648
|
+
Markdown and claims settings come from `pagegraph()` in Vite, including the canonical Markdown
|
|
649
|
+
origin and facts module. Built-mode commands read Vite with `command: "build"` and
|
|
650
|
+
`mode: "production"`; `--dev` reads `command: "serve"` and `mode: "development"`.
|
|
651
|
+
Put CLI policies and the graph loader in `pagegraph.config.ts`.
|
|
404
652
|
|
|
405
653
|
## CLI
|
|
406
654
|
|
|
407
|
-
The graph commands acquire your graph through `
|
|
408
|
-
|
|
409
|
-
|
|
655
|
+
The graph commands acquire your graph through `pagegraph.config.ts` at the app root.
|
|
656
|
+
Rename an existing `seo.config.ts`, `.js`, or `.mjs` to its `pagegraph.config.*` equivalent.
|
|
657
|
+
The file holds CLI policies and `loadGraph`; site identity and robots policy come from the
|
|
658
|
+
loader result, and Markdown and claims settings come from the Vite plugin. A
|
|
659
|
+
TanStack Start app uses `tanstackStartGraph` (above), which also supplies the robots
|
|
660
|
+
policy and site identity from `pagegraph()`, using build settings in production mode by default.
|
|
661
|
+
Pass `command: "serve", mode: "development"` to inspect a development configuration instead.
|
|
662
|
+
Otherwise `viteGraphLoader` evaluates your graph
|
|
663
|
+
module inside a headless Vite server, so path aliases, content plugins, and virtual modules all resolve:
|
|
410
664
|
|
|
411
665
|
```ts
|
|
412
|
-
//
|
|
666
|
+
// pagegraph.config.ts
|
|
413
667
|
import { defineSeoConfig, viteGraphLoader } from "pagegraph/config";
|
|
668
|
+
import { routeConfig } from "./src/lib/route-config";
|
|
414
669
|
|
|
415
670
|
export default defineSeoConfig({
|
|
416
|
-
origin: "https://example.com",
|
|
417
|
-
disallow: routeConfig.robotsExclusions,
|
|
418
|
-
contentSignal: "search=yes, ai-input=yes, ai-train=yes",
|
|
419
671
|
// Fail `check` unless each named money page has enough contextual links.
|
|
420
672
|
coverage: [{ path: "/pricing", minInbound: 2 }, { path: "/features/*", minInbound: 1 }],
|
|
421
673
|
loadGraph: viteGraphLoader({
|
|
422
674
|
root: import.meta.dirname,
|
|
423
675
|
entry: "/lib/seo/graph.ts",
|
|
424
676
|
exportName: "loadSeoGraph",
|
|
677
|
+
site: {
|
|
678
|
+
origin: "https://example.com",
|
|
679
|
+
indexable: true,
|
|
680
|
+
robots: { disallow: routeConfig.robotsExclusions, contentSignal: "search=yes, ai-input=yes, ai-train=yes" },
|
|
681
|
+
},
|
|
425
682
|
}),
|
|
426
683
|
});
|
|
427
684
|
```
|
|
428
685
|
|
|
429
|
-
|
|
430
|
-
its entry export. Existing zero-argument loaders remain valid. The host resolves
|
|
431
|
-
framework-specific route `head()` values into node instances; pagegraph selects
|
|
432
|
-
and validates the resulting plain metadata:
|
|
686
|
+
Build-time consumers of page titles and descriptions read the same graph:
|
|
433
687
|
|
|
434
688
|
```ts
|
|
435
|
-
import { loadPageHeads
|
|
689
|
+
import { loadPageHeads } from "pagegraph/config"; // build-only
|
|
690
|
+
import { tanstackStartGraph } from "pagegraph/tanstack-start";
|
|
436
691
|
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
entry: "/lib/seo/graph.ts",
|
|
440
|
-
exportName: "loadHeadGraph",
|
|
692
|
+
const heads = await loadPageHeads(tanstackStartGraph({ root: import.meta.dirname }), {
|
|
693
|
+
exclude: ["/docs", "/docs/**"],
|
|
441
694
|
});
|
|
442
|
-
const exclude = ["/docs", "/docs/**", "/captured-page"];
|
|
443
|
-
const heads = await loadPageHeads(() => loader({ exclude }), { exclude });
|
|
444
695
|
```
|
|
445
696
|
|
|
446
|
-
`selectPageHeadNodes(graph, options)` selects
|
|
447
|
-
`pageHeads(graph, options)` returns `{ path, title, description }[]
|
|
448
|
-
|
|
697
|
+
`selectPageHeadNodes(graph, options)` selects the page nodes `pageHeads` reads.
|
|
698
|
+
`pageHeads(graph, options)` returns `{ path, title, description }[]` from each node's
|
|
699
|
+
`head` (a declared `staticData.seo.head`, or a collection instance through its route's title
|
|
700
|
+
template), requiring nonblank titles and descriptions and naming the page on failure. `loadPageHeads`
|
|
449
701
|
always disposes the acquired graph, including when validation fails. Page nodes
|
|
450
702
|
carry a sitemap declaration or an instance; layouts are excluded. By default,
|
|
451
703
|
selection excludes noindex pages, redirects and parameter templates, while retaining
|
|
@@ -461,7 +713,7 @@ pagegraph graph # the graph as a tree · --format mermaid | json
|
|
|
461
713
|
pagegraph inspect /pricing # one node: policy, sitemap status, in/out edges
|
|
462
714
|
pagegraph inspect <url> --live # fetch a deployed page, validate its rendered <head>
|
|
463
715
|
pagegraph links verify <url> # crawl served HTML: depth, orphans, declared-vs-rendered
|
|
464
|
-
pagegraph links verify <url> --assert-coverage # also assert
|
|
716
|
+
pagegraph links verify <url> --assert-coverage # also assert pagegraph.config.ts coverage on served anchors
|
|
465
717
|
pagegraph links verify <url> --emit-rendered <file> # save the rendered edge set
|
|
466
718
|
pagegraph links candidates # propose contextual links from the declared graph
|
|
467
719
|
pagegraph research keywords --query "email api" # combine graph, Executor, and Jev evidence
|
|
@@ -477,7 +729,7 @@ Stdout is data, stderr is status — `pagegraph check --json | jq` just works.
|
|
|
477
729
|
`pagegraph links verify` crawls a site's served HTML — through the same DNS-pinned,
|
|
478
730
|
private-IP-blocked HTTP path as `pagegraph audit` — and reports what a crawler
|
|
479
731
|
actually receives: the real homepage depth, the pages with no incoming internal
|
|
480
|
-
edge (rendered orphans), and, when the app has a `
|
|
732
|
+
edge (rendered orphans), and, when the app has a `pagegraph.config.ts`, the
|
|
481
733
|
declared-vs-rendered link diff. It is bounded with `--limit` and needs no
|
|
482
734
|
framework or config. Discovery reads same-origin `robots.txt` and bounded
|
|
483
735
|
sitemaps before following page anchors, so a sitemap-only page can appear as an
|
|
@@ -532,7 +784,7 @@ It is also a drop-in input for `pagegraph links candidates --rendered`.
|
|
|
532
784
|
|
|
533
785
|
### Assert coverage on the rendered graph
|
|
534
786
|
|
|
535
|
-
`pagegraph check` evaluates the `
|
|
787
|
+
`pagegraph check` evaluates the `pagegraph.config.ts` `coverage` rules against the
|
|
536
788
|
**declared** graph; `pagegraph links verify --assert-coverage` evaluates the same
|
|
537
789
|
rules against the anchors a crawler **actually receives**. Only same-origin
|
|
538
790
|
body-region anchors count — nav, header, and footer links are chrome — which is
|
|
@@ -549,7 +801,7 @@ non-zero) rather than reporting a false pass when the crawl outran `--limit`, a
|
|
|
549
801
|
page failed to fetch (its anchors are missing), a body hit `--max-body-bytes`, the
|
|
550
802
|
robots or sitemap discovery is incomplete, the
|
|
551
803
|
config declares no `coverage`, or the artifact's origin differs from
|
|
552
|
-
`
|
|
804
|
+
`pagegraph.config.ts`. Under `--assert-coverage` the report gains a `coverage` block
|
|
553
805
|
(`{ rules, ok, violations }`); the default `--json` summary is unchanged.
|
|
554
806
|
|
|
555
807
|
### Propose contextual links
|
|
@@ -615,7 +867,7 @@ both `links candidates --site` and `improve links --suggestions`.
|
|
|
615
867
|
|
|
616
868
|
### Contextual-link coverage
|
|
617
869
|
|
|
618
|
-
Declare a contextual-link coverage policy in `
|
|
870
|
+
Declare a contextual-link coverage policy in `pagegraph.config.ts` (a `coverage` array
|
|
619
871
|
of `{ path, minInbound }`), or pass repeatable `--require-inbound "<path-glob>=<n>"`
|
|
620
872
|
flags to override it for one run. `pagegraph check` then fails (exit 1) unless
|
|
621
873
|
every sitemap-eligible page matching the glob has at least `minInbound` incoming
|
|
@@ -709,7 +961,7 @@ undated rather than stale; give content its `modifiedAt` (or a route its
|
|
|
709
961
|
|
|
710
962
|
### Audit any website
|
|
711
963
|
|
|
712
|
-
`pagegraph audit` is framework-independent and does not need `
|
|
964
|
+
`pagegraph audit` is framework-independent and does not need `pagegraph.config.ts`. It
|
|
713
965
|
validates target URLs before making requests, follows redirects through the same
|
|
714
966
|
validation boundary, inspects the rendered document and discovery files, and
|
|
715
967
|
can collect Lighthouse evidence through a validating proxy.
|
|
@@ -772,8 +1024,11 @@ import {
|
|
|
772
1024
|
hasStructuralViolations,
|
|
773
1025
|
inspectHtml,
|
|
774
1026
|
} from "pagegraph";
|
|
1027
|
+
import { evaluateAppGraph } from "pagegraph/tanstack-start";
|
|
775
1028
|
|
|
776
|
-
|
|
1029
|
+
// the graph the plugin ships, evaluated through your Vite config
|
|
1030
|
+
const { graph } = await evaluateAppGraph({ root: appRoot, mode: "production" });
|
|
1031
|
+
expect(hasStructuralViolations(checkGraph(graph))).toBe(false);
|
|
777
1032
|
|
|
778
1033
|
// render a page however you like, then assert the head it actually ships
|
|
779
1034
|
const report = inspectHtml(url, 200, html);
|