Skip to content

feat(docs): serve versioned documentation via Astro middleware#4929

Draft
brian-mulier-p wants to merge 6 commits into
mainfrom
feat/versioned-docs
Draft

feat(docs): serve versioned documentation via Astro middleware#4929
brian-mulier-p wants to merge 6 commits into
mainfrom
feat/versioned-docs

Conversation

@brian-mulier-p

@brian-mulier-p brian-mulier-p commented Jun 4, 2026

Copy link
Copy Markdown
Member

Serve kestra.io/docs/{MAJOR.MINOR}/{path} (e.g. /docs/1.3/...) for past releases by fetching raw markdown from api.kestra.io and rendering it inline through a standalone HTML page. The latest /docs stays the Astro static site.

What

  • Middleware (regex-gated) detects /docs/{major.minor}/... and short-circuits routing. Non-numeric first segments (the latest docs) are left untouched; a 404 from the API redirects to the version home.
  • renderVersionedDoc builds a standalone page (createMarkdownProcessor, shiki off — themes aren't bundled in the Worker) with noindex. remark-directive + a small throw-free transform style the known MDC components (::alert/::collapse/::badge) and let unknown/relic :: blocks degrade to a plain <div> with content preserved (no :: leak), mirroring the Kestra UI MDC unknown-component fallback.
  • MDC component fixes for the API's serve-time output:
    • :::HomePageButtons carries its CTAs in a :buttons='[…]' Vue-bind attribute that is invalid remark-directive syntax (so it leaked as literal text) — now parsed and rendered as a styled link row.
    • Empty-bodied bespoke blocks (HomePageHeader/WhatsNew/BigChildCards/…) are stripped instead of emitting stray empty <div>s.
    • :PluginCount (an inline island) rendered as a block <div> that split its paragraph — unstyled inline directives now render as <span>.
  • Branded navbar matching the real site header: inlined logo (light/dark via prefers-color-scheme), the full top-level nav (Product / Solutions / Plugins / Learn / Company / Pricing) as click-to-toggle dropdowns that collapse into a hamburger drawer on mobile, Contact Sales + Get Started CTAs, and the version selector. One tiny inline, framework-free script drives the menu (no asset graph).
  • versionedDocs helpers: version discovery (>=0.19 integer-compare float guard), API path mapping, href + frontmatter helpers, nav data + HomePageButtons parsing.
  • Version selector added to the latest-docs sidebar (NavSideBar.vue).
  • vitest + 48 unit tests across the two versioned-docs suites, oxlint clean. Verified end-to-end in wrangler dev + astro dev (desktop dropdowns, mobile hamburger drawer, click-toggle path).

Limitations

  • :PluginCount renders empty — the live count is a JS island with no SSR equivalent on this standalone page; the surrounding prose is intact, just without the number.

Closes https://github.com/kestra-io/kestra-ee/issues/7397

Comment thread src/components/docs/NavSideBar.vue Fixed
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

☁️ Cloudflare Worker Preview Deployed!

🔗 https://ks-feat-versioned-docs-docs.kestra-io.workers.dev
🔗 https://3f63495b-docs.kestra-io.workers.dev

## 🔦 Lighthouse Benchmark

Tested: https://ks-feat-versioned-docs-docs.kestra-io.workers.dev on 2026-06-08 16:27 UTC
No baseline available — scores will appear after the first merge to main

Scores (0–100, higher is better)

Page Performance Accessibility Best Practices SEO
Home 68 83 56 92
Pricing 98 91 56 100
Enterprise 97 82 56 100
Cloud 92 87 56 100
About Us 87 91 56 100
Docs Landing 94 88 56 92
Contribute to Kestra (simple docs) 99 88 56 92
Flow (full featured docs) 96 90 56 92
Blog Index 69 91 56 100
Blog Post (sample) 94 87 56 100
VS Page (sample) 98 88 56 100
Plugins Landing 88 81 56 92
Plugin Page (sample) 96 87 56 100
Plugin Debug Page (sample) 93 87 56 100
Plugin Debug Return Page (sample) 93 88 56 100
Blueprints Landing 93 80 56 92
Blueprint Audit Logs CSV Export 66 86 56 100

Core Web Vitals (lower is better)

Page LCP FCP TBT CLS Speed Index
Home 1.50 s 0.74 s 488 ms 0.002 2.58 s
Pricing 1.08 s 0.65 s 16 ms 0.000 0.81 s
Enterprise 1.30 s 0.63 s 8 ms 0.003 0.83 s
Cloud 1.82 s 0.60 s 9 ms 0.000 0.85 s
About Us 2.35 s 0.74 s 12 ms 0.000 0.95 s
Docs Landing 1.52 s 0.55 s 94 ms 0.000 0.96 s
Contribute to Kestra (simple docs) 0.96 s 0.54 s 37 ms 0.000 0.85 s
Flow (full featured docs) 1.34 s 0.57 s 31 ms 0.000 1.02 s
Blog Index 5.37 s 1.21 s 72 ms 0.000 2.16 s
Blog Post (sample) 1.68 s 0.52 s 22 ms 0.004 0.71 s
VS Page (sample) 1.04 s 0.56 s 14 ms 0.005 0.68 s
Plugins Landing 1.55 s 0.56 s 44 ms 0.000 2.91 s
Plugin Page (sample) 1.04 s 0.57 s 42 ms 0.051 1.67 s
Plugin Debug Page (sample) 0.88 s 0.50 s 161 ms 0.001 1.56 s
Plugin Debug Return Page (sample) 1.05 s 0.57 s 132 ms 0.025 1.80 s
Blueprints Landing 1.36 s 0.78 s 17 ms 0.000 1.70 s
Blueprint Audit Logs CSV Export 0.93 s 0.62 s 220 ms 0.485 2.17 s
Legend

🟢 improved  ·  🔻 regressed  ·  (blank) no significant change
Score threshold: ±10 pts  ·  Metric threshold: ±30% of baseline

const onVersionChange = (event: Event) => {
const version = (event.target as HTMLSelectElement).value
if (!version) return
window.location.href = versionedHref(version, window.location.pathname)
@brian-mulier-p brian-mulier-p force-pushed the feat/versioned-docs branch 2 times, most recently from d130fe3 to 405fa71 Compare June 5, 2026 13:36
Serve kestra.io/docs/{MAJOR.MINOR}/{path} (e.g. /docs/1.3/...) for past
releases by fetching raw markdown from api.kestra.io and rendering it
inline through a standalone HTML page, while the latest /docs stays the
Astro static site.

- Middleware (regex-gated) detects /docs/{major.minor}/... and short-
  circuits routing; non-numeric first segments (the latest docs) are left
  untouched; a 404 from the API redirects to the version home.
- renderVersionedDoc builds a standalone page (createMarkdownProcessor,
  shiki off since themes aren't bundled in the Worker) with a version
  selector and noindex. remark-directive plus a small throw-free transform
  style the known MDC components (alert/collapse/badge) and let unknown /
  relic ::blocks degrade to a plain <div> with content preserved (no "::"
  leak), mirroring the Kestra UI MDC unknown-component fallback.
- versionedDocs helpers: version discovery (>=0.19 integer-compare float
  guard), API path mapping, href + frontmatter helpers. The newest version
  is folded into a single "Latest (X)" selector entry (pointing at the
  static /docs) instead of appearing twice.
- Version selector added to the latest-docs sidebar (NavSideBar.vue).
- Version list cached per worker isolate with a 10-minute TTL.
- vitest + 32 unit tests, oxlint clean.

Refs kestra-io/kestra-ee#7397
Fetch the docs-children endpoint per version and render a tree-like nav
sidebar on the standalone JS-less versioned pages. The active leaf gets
aria-current and its ancestors render <details open> server-side, so the
current section is expanded without JS; a small inline script handles the
collapse toggle and scrolls the active link into view.

buildDocTree relies on the parent-prefix invariant (verified across all
served versions) and drops hideSidebar pages and their subtree to match
the latest-docs sidebar. getDocChildren memoizes per version and fails
soft to an empty map, so the page degrades to a single column on error.
Swap the versioned-doc renderer's parser from remark-directive (via
Astro's createMarkdownProcessor) to @nuxtjs/mdc's createMarkdownParser,
keeping JS-less HTML output via a hand-written serializer.

This parses both MDC dialects natively:
- 0.19-0.24 `::` block components
- 1.0/1.1 `:::` fenced components (incl. `:buttons='[...]'` JSON props)

so HomePageButtons CTA rows now actually render on 1.1 (the original
complaint) and on the `::`-era pages, instead of leaking or being
stripped. Drops the HOMEPAGE_BUTTONS_RE / stripUnsupportedMdc
pre-processing those formats needed.

Confined to renderVersionedDoc.ts (+ test) for easy revert; the now-dead
versionedDocs.ts exports are left in place.
The homepage button labels were invisible: `.vd-content a` (link color,
specificity 0,1,1) overrode `.vd-button` (0,1,0), painting the text the
same purple as the background.

Mirror the live HomePageButtons instead — first button primary, the rest
secondary — using `.vd-content .vd-button*` selectors that win the cascade,
with colors from the design tokens (primary #631bff/white with the inset
glow; secondary themed light/dark).
Re-point every asset reference (img/source/video[+poster]/audio) in a
versioned doc page at the versioned asset API, so a versioned page serves
versioned resources — mirroring the in-app ProseImg + doc store.

- isVersionedAssetRef + versionedAssetUrl: root-absolute refs with a file
  extension are rewritten to ${api}/docs${src}/versions/{ver}.0 (prepends the
  /docs domain to the verbatim ref, like the in-app resourceUrl); external,
  protocol-relative and relative refs are left untouched.
- rewriteAssetUrls: tree pre-pass mutating props.src/poster before serialize.
- apiUrl DI param (default api.kestra.io/v1), wired from middleware's API_URL.
Versioned doc pages now reference assets served from api.kestra.io, but the
site CSP img-src had no kestra domain, so the browser blocked every versioned
image. Add https://*.kestra.io (matching script-src/connect-src).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants