pagegraph 0.10.1 → 0.12.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 +163 -124
- package/dist/audit.d.ts +1 -1
- package/dist/audit.js +1 -1
- 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/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/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-uZk5Em9A.js +352 -0
- package/dist/checks-uZk5Em9A.js.map +1 -0
- package/dist/cli.js +3814 -3017
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +10 -45
- package/dist/config.js +10 -98
- package/dist/config.js.map +1 -1
- package/dist/{content-DVL2iyGU.d.ts → content-B1cPtL8b.d.ts} +4 -128
- package/dist/{content-BE9KCTgb.js → content-kcEnQdfi.js} +4 -489
- package/dist/content-kcEnQdfi.js.map +1 -0
- package/dist/declare-D5o7hwmv.js +9 -0
- package/dist/declare-D5o7hwmv.js.map +1 -0
- package/dist/declare-DVgdEdOL.d.ts +12127 -0
- package/dist/graph-Bcsourlv.js +162 -0
- package/dist/graph-Bcsourlv.js.map +1 -0
- package/dist/graph-BxCY90vq.d.ts +104 -0
- package/dist/graph-CBIY71w6.d.ts +57 -0
- package/dist/index.d.ts +37 -73
- package/dist/index.js +23 -133
- package/dist/index.js.map +1 -1
- package/dist/message-DT_CjUdt.js +12 -0
- package/dist/message-DT_CjUdt.js.map +1 -0
- package/dist/page-heads-6y-CARRQ.js +25 -0
- package/dist/page-heads-6y-CARRQ.js.map +1 -0
- package/dist/page-heads-DNkjMUEN.d.ts +21 -0
- package/dist/projections-D0oOFKiY.js +142 -0
- package/dist/projections-D0oOFKiY.js.map +1 -0
- package/dist/projections-DeEFe7XT.d.ts +78 -0
- package/dist/react.d.ts +26 -12051
- package/dist/react.js +12 -3
- 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 +40 -0
- package/dist/tanstack-start/index.js +368 -0
- package/dist/tanstack-start/index.js.map +1 -0
- package/dist/tanstack-start/server.d.ts +14 -0
- package/dist/tanstack-start/server.js +53 -0
- package/dist/tanstack-start/server.js.map +1 -0
- package/dist/vite-graph-loader-01p1WxEq.js +104 -0
- package/dist/vite-graph-loader-01p1WxEq.js.map +1 -0
- package/dist/vite-graph-loader-Btgjz5RH.d.ts +56 -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 +35 -13
- package/dist/content-BE9KCTgb.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,23 +26,43 @@ 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` | `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
|
+
## What runs where
|
|
36
30
|
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
Every entry belongs to one place. Runtime entries are safe in a Worker, an SSR server, or a
|
|
32
|
+
browser; build entries load your app through Vite, parse routes natively, or drive Node I/O.
|
|
33
|
+
|
|
34
|
+
| Entry | Runs in | Exports | Peers |
|
|
35
|
+
| ------------------ | ----------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------- |
|
|
36
|
+
| `pagegraph` | runtime (Worker, SSR, browser) | `buildSeoGraph`, `contentCollection`, `renderSitemap`, `renderRobots`, `pageHeads`, `graphToJson`/`graphFromJson`, checks, `inspectHtml` | — |
|
|
37
|
+
| `pagegraph/react` | runtime | `createSeo` → `seo.head`, `seoHead`, `Breadcrumbs`, JSON-LD generators | `react`, `@tanstack/react-router` |
|
|
38
|
+
| `pagegraph/tanstack-start/server` | runtime (Start server routes) | `robotsTxt`, `sitemapXml`, `seoGraph`, `seoSite` | the `pagegraph()` plugin |
|
|
39
|
+
| `pagegraph/tanstack-start` | build (`vite.config.ts`, `seo.config.ts`) | `pagegraph()` Vite plugin, `tanstackStartGraph`, `evaluateAppGraph` | `vite`, `@tanstack/router-generator` |
|
|
40
|
+
| `pagegraph/vite` | build (`vite.config.ts`) | `seoRouteConfig` coverage gate | `vite`, `@tanstack/router-generator` |
|
|
41
|
+
| `pagegraph/config` | build (`seo.config.ts`, CLI) | `defineSeoConfig`, `viteGraphLoader`, `loadPageHeads` | `vite` |
|
|
42
|
+
| `pagegraph/audit` | build / CLI (Node or Bun) | Audit services, scanner protocol, rules, and report schemas | `effect` |
|
|
43
|
+
| `pagegraph` bin | CLI (Bun) | CLI over the same graph | bundled |
|
|
44
|
+
|
|
45
|
+
Build entries resolve to a stub under the `workerd`, `worker`, and `browser` export conditions.
|
|
46
|
+
A Worker or browser bundle that reaches one fails at import with an error naming the entry,
|
|
47
|
+
instead of crashing on a native binding when a request first runs the code. The runtime entries
|
|
48
|
+
have **zero runtime dependencies** beyond their listed peers; a test pins each entry's import
|
|
49
|
+
graph.
|
|
39
50
|
|
|
40
51
|
The CLI **bundles PageGraph's Effect and TypeSafe provider runtime**, so it does not depend on the
|
|
41
|
-
app's Effect
|
|
42
|
-
OpenCode SDK
|
|
52
|
+
app's Effect version; it runs on [Bun](https://bun.sh) (`bunx pagegraph`). Agentic workflows load the
|
|
53
|
+
OpenCode SDK only when invoked. The first workflow installs pinned OpenCode 2.0.22 into
|
|
54
|
+
`~/.cache/pagegraph/opencode-2.0.22-2`, outside the app's dependency tree; subsequent workflows
|
|
55
|
+
reuse the verified cache. This keeps consumer dependency overrides away from OpenCode's declared
|
|
56
|
+
Effect and Drizzle versions. Executor plugins must use OpenCode's Effect version (`4.0.0-rc.112`).
|
|
57
|
+
Bun and network access to npm are required for the first install. The pinned OpenCode client's
|
|
58
|
+
HTTP error handling retains the method, path, status, and response body for diagnostics;
|
|
59
|
+
undeclared error bodies are capped at 16 KiB.
|
|
60
|
+
Discovery-only research gets one continuation to complete provider evidence. Every workflow failure
|
|
61
|
+
after run creation writes `failure.json` in the run directory, with the stage, session ID when available,
|
|
62
|
+
transcript, and cause chain. `vite` stays a peer — graph commands
|
|
43
63
|
load your app through Vite at runtime — and `lighthouse` is only needed by `pagegraph audit`.
|
|
44
|
-
Importing `pagegraph/audit` programmatically
|
|
64
|
+
Importing `pagegraph/audit` programmatically needs `effect@^4.0.0`. The Effect and TypeSafe
|
|
65
|
+
peers remain optional for other library entry points and the bundled CLI.
|
|
45
66
|
|
|
46
67
|
### Agent skill
|
|
47
68
|
|
|
@@ -54,7 +75,7 @@ pagegraph skills get core
|
|
|
54
75
|
|
|
55
76
|
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.
|
|
56
77
|
|
|
57
|
-
## Quick start
|
|
78
|
+
## Quick start (TanStack Start)
|
|
58
79
|
|
|
59
80
|
**1. Bind your site identity once.** Route files never see an origin or a brand name.
|
|
60
81
|
|
|
@@ -62,7 +83,7 @@ The skill is maintained at [skill-data/core/SKILL.md](skill-data/core/SKILL.md)
|
|
|
62
83
|
// lib/seo.ts
|
|
63
84
|
import { createSeo } from "pagegraph/react";
|
|
64
85
|
|
|
65
|
-
|
|
86
|
+
const site = createSeo({
|
|
66
87
|
origin: "https://example.com",
|
|
67
88
|
site: {
|
|
68
89
|
name: "Example",
|
|
@@ -76,19 +97,17 @@ export const { seoHead } = createSeo({
|
|
|
76
97
|
description: "What the company does.",
|
|
77
98
|
sameAs: ["https://github.com/example"],
|
|
78
99
|
contactPoint: { contactType: "support", email: "hi@example.com" },
|
|
79
|
-
address: {
|
|
80
|
-
streetAddress: "123 Example Street",
|
|
81
|
-
addressLocality: "Example City",
|
|
82
|
-
addressRegion: "CA",
|
|
83
|
-
postalCode: "94105",
|
|
84
|
-
addressCountry: "US",
|
|
85
|
-
},
|
|
86
100
|
},
|
|
87
101
|
website: { searchPath: "/docs?q={search_term_string}" },
|
|
88
102
|
});
|
|
103
|
+
|
|
104
|
+
export const seo = site; // seo.head for static routes
|
|
105
|
+
export const { seoHead } = site;
|
|
89
106
|
```
|
|
90
107
|
|
|
91
|
-
**2. Declare
|
|
108
|
+
**2. Declare each page once.** `staticData.seo` holds the crawl policy and the head
|
|
109
|
+
together; `seo.head` renders it. The graph and the checks read the same
|
|
110
|
+
`staticData.seo.head` — the page and the graph cannot disagree.
|
|
92
111
|
|
|
93
112
|
```tsx
|
|
94
113
|
export const Route = createFileRoute("/pricing")({
|
|
@@ -100,124 +119,112 @@ export const Route = createFileRoute("/pricing")({
|
|
|
100
119
|
related: ["/features", "/docs"],
|
|
101
120
|
link: { title: "Pricing", description: "Simple volume pricing." },
|
|
102
121
|
modifiedAt: "2026-09-29", // sitemap <lastmod> and the freshness report
|
|
122
|
+
head: {
|
|
123
|
+
title: "Pricing — Example",
|
|
124
|
+
description: "Simple volume pricing.",
|
|
125
|
+
faqs: pricingFaqs,
|
|
126
|
+
},
|
|
103
127
|
},
|
|
104
128
|
},
|
|
105
|
-
head:
|
|
106
|
-
|
|
107
|
-
title: "Pricing — Example",
|
|
108
|
-
description: "Simple volume pricing.",
|
|
109
|
-
}),
|
|
129
|
+
head: seo.head,
|
|
130
|
+
component: PricingPage,
|
|
110
131
|
});
|
|
111
132
|
```
|
|
112
133
|
|
|
113
|
-
`
|
|
134
|
+
`seo.head` returns the `meta` + canonical `links` TanStack renders into `<head>` —
|
|
114
135
|
title, description, og/twitter cards, robots, and the JSON-LD each page warrants
|
|
115
|
-
(BreadcrumbList always; Article, FAQPage, Service, ItemList when the
|
|
116
|
-
|
|
136
|
+
(BreadcrumbList always; Article, FAQPage, Service, ItemList when the head declares
|
|
137
|
+
them). A route that renders from `loaderData` calls `seoHead(ctx, instance)` in its
|
|
138
|
+
own `head()` and declares a `titleTemplate` (`"%s | Example Blog"`): `seoHead` applies it,
|
|
139
|
+
and the graph applies it to the route's collection pages.
|
|
117
140
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
`schema-dts` entity, link it to the plugin's stable site identities, and compose one
|
|
122
|
-
site graph:
|
|
141
|
+
**3. Register the plugin.** It builds the graph once per build (and on change in dev)
|
|
142
|
+
inside your app's Vite pipeline — path aliases, `define`s, and content plugins such as
|
|
143
|
+
Fumadocs apply, with no stubs — and ships it to the server runtime as data.
|
|
123
144
|
|
|
124
145
|
```ts
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
"@id": "https://example.com/#product",
|
|
139
|
-
name: "Example",
|
|
140
|
-
applicationCategory: "DeveloperApplication",
|
|
141
|
-
operatingSystem: "Web",
|
|
142
|
-
provider: jsonLdRef(ids.organization),
|
|
143
|
-
}),
|
|
144
|
-
],
|
|
145
|
-
transform: (entry) => {
|
|
146
|
-
if (entry.kind === "organization") {
|
|
147
|
-
return extendJsonLd(entry.document, {
|
|
148
|
-
slogan: "Ship with confidence.",
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
return entry.document;
|
|
152
|
-
},
|
|
146
|
+
// vite.config.ts
|
|
147
|
+
import { pagegraph } from "pagegraph/tanstack-start";
|
|
148
|
+
|
|
149
|
+
pagegraph({
|
|
150
|
+
origin: "https://example.com",
|
|
151
|
+
indexable: process.env.DEPLOY_ENV === "production", // previews disallow everything
|
|
152
|
+
robots: { contentSignal: "search=yes, ai-input=yes, ai-train=yes" },
|
|
153
|
+
collections: "src/lib/seo/collections.ts",
|
|
154
|
+
routeConfig: {
|
|
155
|
+
outputPath: fileURLToPath(new URL("./src/lib/route-config.ts", import.meta.url)),
|
|
156
|
+
publicGroups: ["(marketing)", "(docs)"],
|
|
157
|
+
enforceCoverageIn: ["(marketing)", "(docs)"],
|
|
158
|
+
alwaysDisallow: ["/dashboard", "/api"],
|
|
153
159
|
},
|
|
154
160
|
});
|
|
155
|
-
|
|
156
|
-
const siteGraph = seo.generateSiteGraphSchema();
|
|
157
161
|
```
|
|
158
162
|
|
|
159
|
-
`generateSiteGraphSchema()` includes Organization, WebSite, and configured site
|
|
160
|
-
entities in one `@graph`. Organization and WebSite use stable `#organization` and
|
|
161
|
-
`#website` IDs; articles and services provided by the site reference the same
|
|
162
|
-
Organization. Existing individual generators remain available and are not transformed.
|
|
163
|
-
|
|
164
|
-
Add page-specific entities through `seoHead`:
|
|
165
|
-
|
|
166
163
|
```ts
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
164
|
+
// src/lib/seo/collections.ts — evaluated in the app's Vite pipeline
|
|
165
|
+
import { contentCollection } from "pagegraph";
|
|
166
|
+
import { blogSource } from "../blog-source"; // e.g. a Fumadocs loader()
|
|
167
|
+
|
|
168
|
+
export const collections = () => [
|
|
169
|
+
contentCollection({
|
|
170
|
+
route: "/blog/$slug",
|
|
171
|
+
source: "blog",
|
|
172
|
+
pages: blogSource.getPages(),
|
|
173
|
+
entry: (page) => ({
|
|
174
|
+
title: page.data.title,
|
|
175
|
+
description: page.data.description,
|
|
176
|
+
publishedAt: page.data.date,
|
|
177
|
+
related: page.data.related,
|
|
178
|
+
}),
|
|
179
|
+
}),
|
|
180
|
+
];
|
|
179
181
|
```
|
|
180
182
|
|
|
181
|
-
The
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
The plugin evaluates every route file that declares `staticData` — never the generated
|
|
184
|
+
route tree — in a Node environment named `pagegraph`. A route that imports a module
|
|
185
|
+
only the server runtime provides (`cloudflare:workers`) fails with its file name; add an
|
|
186
|
+
app-only route to `exclude` (route-file globs).
|
|
185
187
|
|
|
186
|
-
**
|
|
188
|
+
**4. Serve robots.txt and sitemap.xml.** One line each; the output depends on the
|
|
189
|
+
deployment's origin and indexability, so they stay routes.
|
|
187
190
|
|
|
188
191
|
```ts
|
|
189
|
-
//
|
|
190
|
-
import {
|
|
192
|
+
// src/routes/robots[.]txt.ts — sitemap[.]xml.ts is symmetric with sitemapXml
|
|
193
|
+
import { robotsTxt } from "pagegraph/tanstack-start/server";
|
|
191
194
|
|
|
192
|
-
export const
|
|
193
|
-
buildSeoGraph({ routeTree, collections: [blogCollection] });
|
|
195
|
+
export const Route = createFileRoute("/robots.txt")({ server: { handlers: { GET: robotsTxt } } });
|
|
194
196
|
```
|
|
195
197
|
|
|
198
|
+
**5. Gate CI.** The CLI reads the same graph through `seo.config.ts`:
|
|
199
|
+
|
|
196
200
|
```ts
|
|
197
|
-
//
|
|
198
|
-
import {
|
|
201
|
+
// seo.config.ts
|
|
202
|
+
import { defineSeoConfig } from "pagegraph/config";
|
|
203
|
+
import { tanstackStartGraph } from "pagegraph/tanstack-start";
|
|
199
204
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
indexable: true,
|
|
204
|
-
disallow: routeConfig.robotsExclusions,
|
|
205
|
-
contentSignal: "search=yes, ai-input=yes, ai-train=yes",
|
|
205
|
+
export default defineSeoConfig({
|
|
206
|
+
origin: "https://example.com",
|
|
207
|
+
loadGraph: tanstackStartGraph({ root: import.meta.dirname }),
|
|
206
208
|
});
|
|
207
209
|
```
|
|
208
210
|
|
|
211
|
+
```bash
|
|
212
|
+
pagegraph check
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Without TanStack Start
|
|
216
|
+
|
|
217
|
+
`buildSeoGraph({ routeTree, collections })` builds the same graph from any TanStack
|
|
218
|
+
Router tree; render it with `renderSitemap(graph, { origin, indexable })` and
|
|
219
|
+
`renderRobots(graph, { origin, indexable, disallow, contentSignal })`, and point
|
|
220
|
+
`seo.config.ts` at it with `viteGraphLoader` (see [CLI](#cli)).
|
|
221
|
+
|
|
209
222
|
`renderRobots` does not invent a Content-Signal. Pass `contentSignal` for
|
|
210
223
|
the value (the plugin prefixes `Content-Signal: `), `directives` for extra
|
|
211
224
|
group lines, and `transform` if you need to wrap or replace the whole file.
|
|
212
225
|
Preview hosts (`indexable: false`) drop `contentSignal` and `directives`;
|
|
213
226
|
`transform` still runs.
|
|
214
227
|
|
|
215
|
-
**4. Gate CI.** The same graph, the same rules, exit 1 on structural violations.
|
|
216
|
-
|
|
217
|
-
```bash
|
|
218
|
-
pagegraph check
|
|
219
|
-
```
|
|
220
|
-
|
|
221
228
|
## Agentic workflows
|
|
222
229
|
|
|
223
230
|
PageGraph can combine the deterministic route graph with a project-owned embedded OpenCode agent,
|
|
@@ -285,6 +292,11 @@ After research is validated, the run directory contains `research.json`: an inco
|
|
|
285
292
|
with the collected evidence, decision inputs, and OpenCode transcript. A downstream failure reports
|
|
286
293
|
its path so the evidence remains available for diagnosis. Only a completed workflow writes
|
|
287
294
|
`run.json` and `summary.md`; a research checkpoint is not a final recommendation.
|
|
295
|
+
Model JSON drops null properties and array elements before validation. Schema validation collects all
|
|
296
|
+
issues for one read-only repair turn. Every error after run creation writes `failure.json` in that
|
|
297
|
+
run directory with the workflow, stage, available OpenCode session ID, transcript, and full cause
|
|
298
|
+
chain. The error reports the artifact path and a next step; a failed artifact write preserves the
|
|
299
|
+
original error with a note.
|
|
288
300
|
|
|
289
301
|
| Command | Result |
|
|
290
302
|
| --- | --- |
|
|
@@ -373,9 +385,10 @@ route's date is never inherited by its instances. Undated pages emit no
|
|
|
373
385
|
|
|
374
386
|
## Coverage gate (Vite)
|
|
375
387
|
|
|
376
|
-
`
|
|
377
|
-
|
|
378
|
-
|
|
388
|
+
`pagegraph({ routeConfig })` — or `seoRouteConfig` from `pagegraph/vite` in a Router-only
|
|
389
|
+
app — parses the route tree with `@tanstack/router-generator` (the same parser as the
|
|
390
|
+
router) and fails `vite build` when a page in an enforced group has neither `staticData`
|
|
391
|
+
nor `head`:
|
|
379
392
|
|
|
380
393
|
```ts
|
|
381
394
|
// vite.config.ts
|
|
@@ -390,14 +403,15 @@ seoRouteConfig({
|
|
|
390
403
|
```
|
|
391
404
|
|
|
392
405
|
It also derives a small config module from the route tree: `robotsExclusions`
|
|
393
|
-
(
|
|
394
|
-
not hand out as tenant/org slugs).
|
|
406
|
+
(the plugin adds them to robots.txt; a Router-only app feeds them to `renderRobots`) and
|
|
407
|
+
`reservedSegments` (top-level segments an app must not hand out as tenant/org slugs).
|
|
395
408
|
|
|
396
409
|
## CLI
|
|
397
410
|
|
|
398
|
-
The graph commands acquire your graph through `seo.config.ts` at the app root
|
|
399
|
-
|
|
400
|
-
|
|
411
|
+
The graph commands acquire your graph through `seo.config.ts` at the app root. A
|
|
412
|
+
TanStack Start app uses `tanstackStartGraph` (above), which also supplies the robots
|
|
413
|
+
policy. Otherwise `viteGraphLoader` evaluates your graph module inside a headless Vite
|
|
414
|
+
server, so path aliases, content plugins, and virtual modules all resolve:
|
|
401
415
|
|
|
402
416
|
```ts
|
|
403
417
|
// seo.config.ts
|
|
@@ -417,6 +431,28 @@ export default defineSeoConfig({
|
|
|
417
431
|
});
|
|
418
432
|
```
|
|
419
433
|
|
|
434
|
+
Build-time consumers of page titles and descriptions read the same graph:
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
import { loadPageHeads } from "pagegraph/config"; // build-only
|
|
438
|
+
import { tanstackStartGraph } from "pagegraph/tanstack-start";
|
|
439
|
+
|
|
440
|
+
const heads = await loadPageHeads(tanstackStartGraph({ root: import.meta.dirname }), {
|
|
441
|
+
exclude: ["/docs", "/docs/**"],
|
|
442
|
+
});
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
`selectPageHeadNodes(graph, options)` selects the page nodes `pageHeads` reads.
|
|
446
|
+
`pageHeads(graph, options)` returns `{ path, title, description }[]` from each node's
|
|
447
|
+
`head` (a declared `staticData.seo.head`, or a collection instance through its route's title
|
|
448
|
+
template), requiring nonblank titles and descriptions and naming the page on failure. `loadPageHeads`
|
|
449
|
+
always disposes the acquired graph, including when validation fails. Page nodes
|
|
450
|
+
carry a sitemap declaration or an instance; layouts are excluded. By default,
|
|
451
|
+
selection excludes noindex pages, redirects and parameter templates, while retaining
|
|
452
|
+
sitemap-disabled hubs. Set `indexable: false` to include those nonindexable nodes.
|
|
453
|
+
`exclude` accepts exact paths and path globs (`*` within a segment, `**` across
|
|
454
|
+
segments). Docs are ordinary pages; excluding them is caller policy, not a graph kind.
|
|
455
|
+
|
|
420
456
|
```bash
|
|
421
457
|
pagegraph check # CI gate — exit 1 on structural violations
|
|
422
458
|
pagegraph check --site <url> # also check every declared page's rendered HTML
|
|
@@ -736,8 +772,11 @@ import {
|
|
|
736
772
|
hasStructuralViolations,
|
|
737
773
|
inspectHtml,
|
|
738
774
|
} from "pagegraph";
|
|
775
|
+
import { evaluateAppGraph } from "pagegraph/tanstack-start";
|
|
739
776
|
|
|
740
|
-
|
|
777
|
+
// the graph the plugin ships, evaluated through your Vite config
|
|
778
|
+
const { graph } = await evaluateAppGraph({ root: appRoot, mode: "production" });
|
|
779
|
+
expect(hasStructuralViolations(checkGraph(graph))).toBe(false);
|
|
741
780
|
|
|
742
781
|
// render a page however you like, then assert the head it actually ships
|
|
743
782
|
const report = inspectHtml(url, 200, html);
|
package/dist/audit.d.ts
CHANGED
package/dist/audit.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { l as extractAnchors, r as extractPageContent, t as checkPageContent } from "./content-
|
|
1
|
+
import { l as extractAnchors, r as extractPageContent, t as checkPageContent } from "./content-kcEnQdfi.js";
|
|
2
2
|
import { mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import * as Context from "effect/Context";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","names":[],"sources":["../../src/build-only/audit.ts"],"sourcesContent":["import { buildOnlyError } from \"./message\";\n\nthrow buildOnlyError(\"pagegraph/audit\");\n"],"mappings":";;AAEA,MAAM,eAAe,iBAAiB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","names":[],"sources":["../../src/build-only/config.ts"],"sourcesContent":["import { buildOnlyError } from \"./message\";\n\nthrow buildOnlyError(\"pagegraph/config\");\n"],"mappings":";;AAEA,MAAM,eAAe,kBAAkB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tanstack-start.js","names":[],"sources":["../../src/build-only/tanstack-start.ts"],"sourcesContent":["import { buildOnlyError } from \"./message\";\n\nthrow buildOnlyError(\"pagegraph/tanstack-start\");\n"],"mappings":";;AAEA,MAAM,eAAe,0BAA0B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.js","names":[],"sources":["../../src/build-only/vite.ts"],"sourcesContent":["import { buildOnlyError } from \"./message\";\n\nthrow buildOnlyError(\"pagegraph/vite\");\n"],"mappings":";;AAEA,MAAM,eAAe,gBAAgB"}
|