@conduction/docusaurus-preset 3.5.0 → 3.6.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 +29 -0
- package/bin/validate-ai-baseline.mjs +16 -0
- package/package.json +1 -1
- package/src/index.js +85 -12
package/README.md
CHANGED
|
@@ -213,6 +213,35 @@ createConfig({
|
|
|
213
213
|
});
|
|
214
214
|
```
|
|
215
215
|
|
|
216
|
+
## Traditional SEO baseline
|
|
217
|
+
|
|
218
|
+
The same `createConfig` call also wires the traditional-search baseline that pairs with the AI-crawler one. Google, Bing, DuckDuckGo and the AI surfaces those engines feed (Copilot, ChatGPT Search, Perplexity) all benefit.
|
|
219
|
+
|
|
220
|
+
**What's shipped automatically**
|
|
221
|
+
|
|
222
|
+
- **Sitemap with `lastmod`** from file mtime; `priority` and `changefreq` are dropped because Google ignores them. `/page/N/` pagination and `/academy/tags/` thin pages are excluded sitewide so they don't dilute crawl budget.
|
|
223
|
+
- **Footer legal links default to absolute URLs on `www.conduction.nl`** (`/privacy`, `/terms`, `/iso`). Earlier defaults used relative routes that 404'd on every per-app subdomain — the SEO audit found ~645 sitewide broken internal links across the fleet from this single mistake. Marketing sites that self-host these pages pass `legalLinks: { privacy: '/privacy', ... }` to opt back into relative routing.
|
|
224
|
+
- **Search Console / Bing Webmaster / Yandex / Facebook / Pinterest verification meta tags** via `opts.searchConsoleVerification`. Each present token becomes a `<meta>` tag in the global head, which lets a non-DNS-admin teammate verify the property via the console UI:
|
|
225
|
+
|
|
226
|
+
```js
|
|
227
|
+
createConfig({
|
|
228
|
+
// ...
|
|
229
|
+
searchConsoleVerification: {
|
|
230
|
+
google: 'abc123...', // -> <meta name="google-site-verification">
|
|
231
|
+
bing: 'xyz...', // -> <meta name="msvalidate.01">
|
|
232
|
+
yandex: '...', // -> <meta name="yandex-verification">
|
|
233
|
+
facebook: '...', // -> <meta name="facebook-domain-verification">
|
|
234
|
+
pinterest: '...', // -> <meta name="p:domain_verify">
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Known follow-ups (not yet automatic)**
|
|
240
|
+
|
|
241
|
+
- `BreadcrumbList` JSON-LD on every page. The DocBreadcrumbs DOM already renders; the schema needs a theme swizzle. Tracked as a 3.7+ candidate.
|
|
242
|
+
- `TechArticle` JSON-LD on docs pages with `dateModified` from git mtime. Same swizzle scope.
|
|
243
|
+
- Per-page title format. Docusaurus defaults to `{Page} | {Site}` which produces `OpenRegister | OpenRegister` on per-app homepages. Override per page via frontmatter `title:` for now; a `titleFormat` option may land later.
|
|
244
|
+
|
|
216
245
|
## Releasing
|
|
217
246
|
|
|
218
247
|
Releases auto-publish on push to `main`, driven by [semantic-release](https://semantic-release.gitbook.io/) reading [conventional-commit](https://www.conventionalcommits.org/) messages. The [.github/workflows/publish-packages.yml](../.github/workflows/publish-packages.yml) workflow walks every commit since the last `@conduction/docusaurus-preset-v*` tag and decides what to ship:
|
|
@@ -87,6 +87,22 @@ check('sitemap.xml exists and has at least 1 URL', () => {
|
|
|
87
87
|
return {ok: true, msg: `${n} URLs`};
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
+
/* sitemap.xml should ship <lastmod> on every URL. Google treats lastmod
|
|
91
|
+
as the only sitemap-level signal that actually informs recrawl
|
|
92
|
+
priority, and only when it's trustworthy. Sites that ship priority +
|
|
93
|
+
changefreq without lastmod (the Docusaurus default before preset
|
|
94
|
+
3.6.0) get treated as having no freshness signal. */
|
|
95
|
+
check('sitemap.xml emits <lastmod> on URLs', () => {
|
|
96
|
+
const body = readBuild('sitemap.xml');
|
|
97
|
+
const locCount = (body.match(/<loc>/g) || []).length;
|
|
98
|
+
const lastmodCount = (body.match(/<lastmod>/g) || []).length;
|
|
99
|
+
if (locCount === 0) return {ok: false, msg: 'no <loc> entries to compare against'};
|
|
100
|
+
if (lastmodCount === 0) return {ok: false, msg: `0 / ${locCount} URLs have <lastmod> — enable sitemap.lastmod in docusaurus.config`};
|
|
101
|
+
const ratio = lastmodCount / locCount;
|
|
102
|
+
if (ratio < 0.5) return {ok: false, msg: `only ${lastmodCount} / ${locCount} URLs have <lastmod>`};
|
|
103
|
+
return {ok: true, msg: `${lastmodCount} / ${locCount} URLs (${Math.round(ratio * 100)}%)`};
|
|
104
|
+
});
|
|
105
|
+
|
|
90
106
|
/* Helper for the JSON-LD checks below. Docusaurus emits ld+json
|
|
91
107
|
tags via two paths with different attribute ordering: top-level
|
|
92
108
|
headTags renders <script type="..."> first, while Helmet (used
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -160,7 +160,7 @@ function buildWebsiteJsonLd(opts) {
|
|
|
160
160
|
* the site's tags after its own defaults.
|
|
161
161
|
*/
|
|
162
162
|
function buildAiHeadTags(opts) {
|
|
163
|
-
|
|
163
|
+
const tags = [
|
|
164
164
|
{
|
|
165
165
|
tagName: 'script',
|
|
166
166
|
attributes: {type: 'application/ld+json'},
|
|
@@ -172,6 +172,45 @@ function buildAiHeadTags(opts) {
|
|
|
172
172
|
innerHTML: JSON.stringify(buildWebsiteJsonLd(opts)),
|
|
173
173
|
},
|
|
174
174
|
];
|
|
175
|
+
|
|
176
|
+
/* Search Console verification meta tags. Sites pass tokens via
|
|
177
|
+
opts.searchConsoleVerification = { google: '...', bing: '...',
|
|
178
|
+
yandex: '...' }; each present token becomes a meta tag. Verifying
|
|
179
|
+
via meta (vs DNS TXT) lets a non-DNS-admin teammate access Search
|
|
180
|
+
Console / Bing Webmaster Tools. */
|
|
181
|
+
const verification = opts.searchConsoleVerification || {};
|
|
182
|
+
if (verification.google) {
|
|
183
|
+
tags.push({
|
|
184
|
+
tagName: 'meta',
|
|
185
|
+
attributes: {name: 'google-site-verification', content: verification.google},
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (verification.bing) {
|
|
189
|
+
tags.push({
|
|
190
|
+
tagName: 'meta',
|
|
191
|
+
attributes: {name: 'msvalidate.01', content: verification.bing},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (verification.yandex) {
|
|
195
|
+
tags.push({
|
|
196
|
+
tagName: 'meta',
|
|
197
|
+
attributes: {name: 'yandex-verification', content: verification.yandex},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (verification.facebook) {
|
|
201
|
+
tags.push({
|
|
202
|
+
tagName: 'meta',
|
|
203
|
+
attributes: {name: 'facebook-domain-verification', content: verification.facebook},
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (verification.pinterest) {
|
|
207
|
+
tags.push({
|
|
208
|
+
tagName: 'meta',
|
|
209
|
+
attributes: {name: 'p:domain_verify', content: verification.pinterest},
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return tags;
|
|
175
214
|
}
|
|
176
215
|
|
|
177
216
|
/**
|
|
@@ -184,15 +223,34 @@ function buildAiHeadTags(opts) {
|
|
|
184
223
|
* Sites passing their own classic preset config can override by
|
|
185
224
|
* including a `sitemap` key alongside `docs`/`blog`/`theme`.
|
|
186
225
|
*/
|
|
226
|
+
/**
|
|
227
|
+
* Sitemap defaults. Google ignores `changefreq` and `priority` (and has
|
|
228
|
+
* for years; the @docusaurus/plugin-sitemap defaults are wrong on this
|
|
229
|
+
* point). `lastmod` is the only signal Google actually uses, and only
|
|
230
|
+
* if the dates are accurate, so we ship lastmod from file mtime. Bing
|
|
231
|
+
* still reads all three, harmless to omit.
|
|
232
|
+
*
|
|
233
|
+
* Sites with locale-specific tag pages and pagination should keep the
|
|
234
|
+
* exclude list in sync. Pagination (`/page/N/`) and tag pages
|
|
235
|
+
* (`/tags/*/`) are documented Docusaurus duplicate-content traps;
|
|
236
|
+
* we exclude them by default so they neither dilute crawl budget nor
|
|
237
|
+
* confuse AI summarisers.
|
|
238
|
+
*/
|
|
187
239
|
const DEFAULT_SITEMAP_OPTIONS = {
|
|
188
|
-
changefreq:
|
|
189
|
-
priority:
|
|
240
|
+
changefreq: null,
|
|
241
|
+
priority: null,
|
|
242
|
+
lastmod: 'date',
|
|
190
243
|
ignorePatterns: [
|
|
191
244
|
'/academy/tags/**',
|
|
192
245
|
'/nl/academy/tags/**',
|
|
193
246
|
'/en/academy/tags/**',
|
|
194
247
|
'/de/academy/tags/**',
|
|
195
248
|
'/fr/academy/tags/**',
|
|
249
|
+
'/page/**',
|
|
250
|
+
'/nl/page/**',
|
|
251
|
+
'/en/page/**',
|
|
252
|
+
'/de/page/**',
|
|
253
|
+
'/fr/page/**',
|
|
196
254
|
],
|
|
197
255
|
filename: 'sitemap.xml',
|
|
198
256
|
};
|
|
@@ -489,9 +547,15 @@ function createConfig(opts) {
|
|
|
489
547
|
footerBrand: opts.footerBrand || null,
|
|
490
548
|
/* Legal-bar links (Privacy / Terms / ISO) plus the two ISO
|
|
491
549
|
9001 + 27001 certification badges on the right side of the
|
|
492
|
-
canal-footer.
|
|
493
|
-
|
|
494
|
-
|
|
550
|
+
canal-footer.
|
|
551
|
+
|
|
552
|
+
Defaults point at the canonical Conduction pages on
|
|
553
|
+
www.conduction.nl rather than relative routes. Earlier
|
|
554
|
+
defaults used /privacy, /terms, /iso which 404'd on every
|
|
555
|
+
per-app subdomain (openregister.conduction.nl/privacy etc.)
|
|
556
|
+
because those routes only exist on the marketing site. The
|
|
557
|
+
SEO audit found ~645 sitewide broken internal links across
|
|
558
|
+
the fleet from this single mistake. Sites can override per
|
|
495
559
|
slot to silence broken-link warnings:
|
|
496
560
|
|
|
497
561
|
legalLinks: {
|
|
@@ -499,12 +563,21 @@ function createConfig(opts) {
|
|
|
499
563
|
terms: false, // hide the Terms link
|
|
500
564
|
iso: false, // hide the ISO link AND the cert badges
|
|
501
565
|
// (badges follow iso link by default)
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
566
|
+
privacy: '/privacy', // self-host: pass a relative route
|
|
567
|
+
certifications: true | false,
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
The marketing site at conduction-website passes legalLinks
|
|
571
|
+
explicitly with relative routes so its self-hosted Privacy /
|
|
572
|
+
Terms / ISO pages keep working as before. */
|
|
573
|
+
legalLinks: Object.assign(
|
|
574
|
+
{
|
|
575
|
+
privacy: 'https://www.conduction.nl/privacy',
|
|
576
|
+
terms: 'https://www.conduction.nl/terms',
|
|
577
|
+
iso: 'https://www.conduction.nl/iso',
|
|
578
|
+
},
|
|
579
|
+
opts.legalLinks || {}
|
|
580
|
+
),
|
|
508
581
|
/* AI-friendly social-card defaults. `image` ships from the
|
|
509
582
|
preset's static/img/og-conduction.png and gets served at every
|
|
510
583
|
consuming site's /img/og-conduction.png; drop your own
|