Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.

feat: AI-crawler-friendly metadata + cobalt hero across apps/solutions/sidecars#82

Closed
rubenvdlinde wants to merge 7 commits into
developmentfrom
feat/ai-crawler-friendly
Closed

feat: AI-crawler-friendly metadata + cobalt hero across apps/solutions/sidecars#82
rubenvdlinde wants to merge 7 commits into
developmentfrom
feat/ai-crawler-friendly

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Collaborator

Summary

Bundles two themes that have been developed on the same branch:

SEO / AI-crawler baseline (5 commits)

  • robots.txt, llms.txt, JSON-LD, OG card metadata for AI crawlers
  • AI-baseline validation gate (hard-fail on regression)
  • per-app ledes and FAQ blocks (preset 3.4.0+)
  • CI workflow that validates the AI baseline on every PR
  • em-dash cleanup in robots.txt prose

Cobalt hero rollout (2 commits)

  • All 17 app pages, all 6 solution pages, and all 8 sidecar pages now pass background="cobalt" to <DetailHero/>, matching the per-app docs sites (openregister.conduction.nl etc).
  • The 15 app pages that carried a standalone prose paragraph between <DetailHero/> and the first <Section> now pass that prose via the new intro={<>...</>} prop. Previously the prose sat at MDX-root and stretched edge-to-edge (no width container on marketing pages). The preset's .intro rule constrains it to a 70ch read measure inside the cobalt panel.
  • Bumps @conduction/docusaurus-preset to ^3.11.0 for the intro prop support.

Test plan

  • Pages build clean
  • AI-baseline CI gate passes
  • /apps/openregister/, /solutions/woo/, /sidecars/n8n/ render the cobalt hero with proper containment (locally verified)
  • No regression on the on-cream hero (no consumer of the intro prop on a non-cobalt hero in this PR)

…SON-LD, OG card)

Conduction.nl was technically crawlable (SSG, no JS-rendering risk) but
commercially invisible to AI agents: no robots.txt, no llms.txt, zero
JSON-LD, no og:image, Dutch sitemap stale. This change ships the AI-
crawling baseline so we get cited in ChatGPT/Claude/Perplexity answers
and our content gets pulled into long-term training corpora — both, on
purpose, posture documented in static/robots.txt.

Changes
- static/robots.txt — explicit allow for OAI-SearchBot, Claude-SearchBot,
  PerplexityBot, ChatGPT-User, Claude-User, GPTBot, ClaudeBot, CCBot and
  friends. Two Sitemap: lines (en + nl) for locale-suffix-blind crawlers.
- static/llms.txt — llmstxt.org-format index pointing at the 15 shipping
  apps plus company/contact tells. Dev-tool consumers (Cursor, Aider,
  MCP doc servers) read this; AI-search adoption is thin but the cost is
  near zero.
- docusaurus.config.js — wraps createConfig() return to inject
  Organization + WebSite JSON-LD via headTags[] on every page, sets
  themeConfig.image to the new OG card, and adds twitter:site /
  twitter:card / og:type baselines via themeConfig.metadata. Also adds
  sitemap options to the classic preset (changefreq, priority, ignore
  /academy/tags/** in both locales) so the sitemap is consistent across
  /sitemap.xml and /nl/sitemap.xml.
- static/img/og-conduction.png — new 1200x630 brand OG card. Cobalt
  #4376FC background, point-up hex motif per brand rules, white
  wordmark, EUPL-1.2 / ISO 27001 / Made in Amsterdam tells.

Out of scope (filed separately): SoftwareApplication JSON-LD per
/apps/<slug> page, FAQPage schema on /support, fleet rollout to the 17
per-app docs sites and to the @conduction/docusaurus-preset.

Verified: local production build emits both JSON-LD scripts in raw HTML
on / and /apps/, og:image + twitter:* meta on every page, og:type stays
'article' on blog posts (not overridden), and both sitemap.xml +
nl/sitemap.xml carry 67 URLs each with tag pages excluded.
Conduction writing rules ban em-dashes in published copy. robots.txt
counts as published copy (served at conduction.nl/robots.txt and read
by every AI crawler). Two em-dashes in the comment header replaced
with a colon and a period.
CI now blocks any change that drops the AI-crawler baseline. The
validation script runs as a postbuild step so every `npm run build`
(local dev + CI) re-asserts the contract.

Checks (12, all hard-fail)
- robots.txt: exists, has Sitemap lines, allows OAI-SearchBot +
  Claude-SearchBot + PerplexityBot, has the default User-agent: *
  Allow: / block.
- llms.txt: exists, >= 1 KB (catches an accidental placeholder).
- sitemap.xml + nl/sitemap.xml: each emit >= 50 URLs.
- Homepage: >= 2 JSON-LD blocks, every block valid JSON, includes
  Organization and WebSite @type entries.
- Homepage: og:image, og:type, twitter:site, twitter:card meta tags
  present. og:image URL resolves to an actual file in the build (so
  LinkedIn / Slack / AI previews never render a 404).
- /apps/openregister: has SoftwareApplication JSON-LD with
  applicationCategory and operatingSystem populated. This is the
  regression marker for the DetailHero schema emission that ships from
  @conduction/docusaurus-preset >= 3.4.0.

Wiring
- postbuild npm script runs scripts/validate-ai-baseline.mjs.
- validate:ai-baseline npm script for ad-hoc runs.
- @conduction/docusaurus-preset bumped to ^3.4.0 so the
  SoftwareApplication check has a guaranteed source.

Adding a check: append to the CHECKS list in
scripts/validate-ai-baseline.mjs. Keep the failure message specific
so a CI annotation tells the reader exactly what regressed.
…locks

PR 1 shipped @conduction/docusaurus-preset 3.4.0 with AI-baseline
defaults (Organization JSON-LD, SoftwareApplication via DetailHero,
FAQPage via FAQ, postBuild robots.txt fallback, og:image, twitter
meta, sitemap options). This commit makes conduction.nl actually
consume that baseline and adds the citation-friendly content layer
on top.

Changes
- docusaurus.config.js: drop the hand-rolled headTags wrap, the
  inline Organization / WebSite JSON-LD constants, and the
  themeConfig.image / themeConfig.metadata overrides. All of it
  comes from createConfig() now. ~80 lines removed.
- src/pages/apps/*.mdx (15 files): Wikipedia-style 1-paragraph lede
  injected immediately under <DetailHero/> on every shipping app
  page. Each lede follows the template "<Name> is an open-source
  <category> app for the Nextcloud workspace. <Capabilities.>
  <Distinguishing positioning.> Released under EUPL-1.2 and
  maintained by Conduction since 2019." Sourced from each app's
  own docs site / repo, then edited for brand voice (no em-dashes,
  one claim per sentence, concrete verbs).
- src/pages/install.mdx: new 7-question FAQ block (admin install,
  Nextcloud version range, multi-app install, offline install,
  uninstall + data, missing-from-store, SLA / support).
- src/pages/iso.mdx: new 6-question FAQ block (9001 vs 27001 scope,
  self-host coverage, BIO alignment, DigiD, commonground.nu stack,
  certificate request flow).
- scripts/validate-ai-baseline.mjs: three more checks pin the
  FAQPage schema to >= 5 questions each on /support, /install, /iso.
  Hard-fail on regression. Validation now runs 15 checks total
  (was 12).

Notes
- /support already shipped a 6-question <FAQ>. Now that preset 3.4.0
  emits FAQPage JSON-LD automatically, the existing block validates
  without any source change.
- mydash README declares AGPL-3.0 while the lede template ends with
  EUPL-1.2. Filed as a follow-up to verify which is canonical; the
  lede currently says EUPL-1.2 to match brand positioning.
- softwarecatalog has two readings in its docs (IT-asset management
  vs internal user-sync). The lede goes with the conduction.nl
  positioning (IT-asset management), which matches the existing
  tagline in apps-catalog.js.
The centralised ConductionNL/.github documentation workflow only
builds on push, schedule, and workflow_dispatch. That meant a PR
could drop robots.txt, break a sitemap, or remove SoftwareApplication
JSON-LD without the validate-ai-baseline.mjs check ever running
in CI. The defect lands; nightly cron catches it the next morning;
production is already broken.

This workflow runs npm run ci (= npm ci --legacy-peer-deps && npm
run build) on every pull_request to development or documentation.
The build's existing postbuild step runs the 15-check validator,
which exits non-zero on regression. A follow-up explicit invocation
acts as belt-and-braces in case anyone removes the postbuild hook.

GITHUB_TOKEN is passed so the prebuild app-downloads fetch stays
under the 60-req/hr unauthenticated rate limit (same pattern the
centralised workflow uses).
Adds background="cobalt" to <DetailHero/> on all 17 app pages, all 6
solution pages, and all 8 sidecar pages so the marketing hero matches
the per-app docs sites (openregister.conduction.nl etc).

For the 15 app pages that carried a standalone prose paragraph between
<DetailHero/> and the first <Section> (the SEO/intro blurb), that prose
is now passed via the new intro={<>...</>} prop on DetailHero. Previously
the prose sat at MDX-root and stretched edge-to-edge because the
marketing-page MDXPage swizzle deliberately has no width container;
inside the hero, the preset's .intro rule constrains it to a 70ch read
measure and renders it cobalt-100 on the cobalt panel.

Requires @conduction/docusaurus-preset version that ships the intro
prop (design-system main 92549b0). Until that preset version is on npm,
the intro paragraphs will render but as an unrecognised prop (silently
dropped); coordinate the npm publish + package.json bump before merging
this commit into a release branch.
The 3.11.0 preset ships the new DetailHero intro prop consumed by the
31 marketing pages in the previous commit.
@rubenvdlinde

Copy link
Copy Markdown
Collaborator Author

Superseded by a clean branch off development. The original branch had 5 SEO commits that duplicated work already merged via #55, causing irreconcilable merge conflicts. New PR coming with just the 2 hero-related commits cherry-picked off this branch.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant