This is an automated email from the ASF dual-hosted git repository.

moonming pushed a commit to branch feat/astro-docs-wave3
in repository https://gitbox.apache.org/repos/asf/apisix-website.git

commit 478fe7182cfa7506d1e35ad3d8f256c6ecfbf3c4
Author: Ming Wen <[email protected]>
AuthorDate: Sun Jul 26 17:33:50 2026 +0800

    feat(docs): serve the latest-version docs from the static build (wave 3)
    
    Migrates the current-version project docs to the Astro build. The version
    archives (3.10–3.16, next) stay exactly as Docusaurus produces them.
    
    - CI sparse-clones docs/ from the seven upstream Apache repos so the sync
      script has content to render; a repo that fails to clone yields no pages
      for that project, which the parity gate then catches.
    - The docs overlay is per-page, not per-subtree: under docs/<project>/ the
      version directories sit beside the latest-version pages, so a subtree
      swap would delete the archive. Only paths Astro actually produced are
      copied, and the step asserts 3.16/next are still the Docusaurus build.
    - A parity gate fails the deploy if any version-less page Docusaurus built
      is missing from the Astro build. Version dirs are excluded by design, as
      are docs/*/tags/ — Docusaurus emits those empty (a single self-link,
      absent from the sitemap, linked from no doc page) and the per-page swap
      leaves them in place, so those URLs keep resolving.
    - Algolia DocSearch (the index the current site already uses) loads on
      docs pages only. Every other page stays zero-JS.
    
    Simulated against the live asf-site tree: 3410 → 3416 pages, 0 deleted,
    6 added (new upstream plugin docs), and all 2908 archived version pages
    byte-identical.
---
 .github/workflows/deploy.yml     | 61 +++++++++++++++++++++++++++++++++++++++-
 next/.gitignore                  |  1 +
 next/src/components/Header.astro | 16 +++++++++--
 next/src/layouts/Base.astro      | 11 +++++++-
 next/src/layouts/DocPage.astro   |  1 +
 next/src/styles/global.css       |  7 +++++
 6 files changed, 93 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 35f6672faf1..ca465c1e5f2 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -165,8 +165,21 @@ jobs:
         working-directory: next
         run: |
           npm ci
+          # Project docs live in the upstream Apache repos. Sparse, depth-1
+          # clones of just docs/ keep this to a few seconds each; the sync
+          # script reads .sync/<repo>/docs/{en,zh}/latest and skips any repo
+          # that is absent, so a fetch failure degrades to "no pages for that
+          # project" — which the parity gate below then catches.
+          mkdir -p .sync
+          for repo in apisix apisix-ingress-controller apisix-helm-chart \
+                      apisix-docker apisix-java-plugin-runner \
+                      apisix-go-plugin-runner apisix-python-plugin-runner; do
+            git clone --depth 1 --filter=blob:none --sparse -q \
+              "https://github.com/apache/$repo.git"; ".sync/$repo" || continue
+            (cd ".sync/$repo" && git sparse-checkout set docs) || true
+          done
           # Blog/learning-center/articles markdown is synced from this very
-          # checkout (WEBSITE_REPO); project docs stay un-synced until wave 3.
+          # checkout (WEBSITE_REPO); project docs come from .sync/ above.
           WEBSITE_REPO="$GITHUB_WORKSPACE" node scripts/sync-content.mjs
           npx astro build
 
@@ -200,6 +213,26 @@ jobs:
               echo "NON-HTML files not in the Astro tree and not in the 
feed-carry list:"; echo "$unhandled"; fail=1
             fi
           done
+          # Latest-version docs: every version-less page Docusaurus produced
+          # must exist in the Astro build, or the swap would leave a stale
+          # Docusaurus page behind a URL we claim to own. Versioned dirs are
+          # excluded — they stay Docusaurus by design. So are docs/*/tags/
+          # index pages: Docusaurus emits them empty (one self-link, absent
+          # from the sitemap, linked from no doc page). The per-page swap
+          # leaves them in place, so the URLs keep resolving.
+          for docs_root in docs zh/docs; do
+            [ -d "website/build/$docs_root" ] || continue
+            missing=$(cd "website/build/$docs_root" && find . -name index.html 
\
+              | sed 's|^\./||' \
+              | grep -vE '(^|/)([0-9]+\.[0-9]+|next|v[0-9][^/]*)/' \
+              | grep -vE '(^|/)tags/index\.html$' \
+              | sort | while read -r f; do
+                [ -f "$ROOT/next/dist/$docs_root/$f" ] || echo "$docs_root/$f"
+              done)
+            if [ -n "$missing" ]; then
+              echo "MISSING latest-version docs in the Astro build:"; echo 
"$missing"; fail=1
+            fi
+          done
           exit $fail
 
       - name: Carry the Docusaurus RSS/atom feeds into the Astro tree
@@ -229,6 +262,32 @@ jobs:
             mkdir -p "website/build/$(dirname "$path")"
             cp -R "next/dist/$path" "website/build/$path"
           done
+          # Latest-version docs come from Astro; every versioned directory
+          # (3.16/, next/, v1.6/, …) stays exactly as Docusaurus built it.
+          # Replacement is per-page, not per-subtree: under docs/<project>/ the
+          # version dirs and the latest-version pages sit side by side, so a
+          # subtree swap would delete the archive. Only paths the Astro build
+          # actually produced are touched.
+          for locale_prefix in "" "zh/"; do
+            src="next/dist/${locale_prefix}docs"
+            [ -d "$src" ] || continue
+            (cd "$src" && find . -name index.html) | sed 's|^\./||' | while 
read -r rel; do
+              case "$rel" in
+                # Never touch an archived version directory.
+                */[0-9].[0-9]*/*|*/next/*|*/v[0-9]*/*) continue ;;
+              esac
+              dest="website/build/${locale_prefix}docs/$rel"
+              mkdir -p "$(dirname "$dest")"
+              cp "$src/$rel" "$dest"
+            done
+          done
+          # The version archives must survive untouched.
+          test -f website/build/docs/apisix/3.16/plugins/cors/index.html
+          test -f website/build/docs/apisix/next/plugins/cors/index.html
+          grep -q 'docusaurus' 
website/build/docs/apisix/3.16/plugins/cors/index.html
+          # …and the latest-version pages must now be the Astro build.
+          grep -q '/_astro/' website/build/docs/apisix/plugins/cors/index.html
+          grep -q '/_astro/' 
website/build/zh/docs/apisix/plugins/cors/index.html
           # Fail the deploy if the landing pages are not the Astro build, or
           # if the homepage lost its stylesheet link.
           grep -q 'The same gateway, now for your LLM traffic' 
website/build/index.html
diff --git a/next/.gitignore b/next/.gitignore
index eb5a90df25f..d2b0fe9f85f 100644
--- a/next/.gitignore
+++ b/next/.gitignore
@@ -3,3 +3,4 @@ dist/
 .sync/
 content/
 .astro/
+.sync/
diff --git a/next/src/components/Header.astro b/next/src/components/Header.astro
index 30a0e51f6f8..225f574ba0c 100644
--- a/next/src/components/Header.astro
+++ b/next/src/components/Header.astro
@@ -1,8 +1,8 @@
 ---
 import { NAV, LOGO, localePrefix, type Locale } from '../lib/site';
 
-interface Props { locale: Locale; path: string }
-const { locale, path } = Astro.props;
+interface Props { locale: Locale; path: string; search?: boolean }
+const { locale, path, search = false } = Astro.props;
 const prefix = localePrefix(locale);
 const label = (item: { label: string; labelZh?: string }) =>
   locale === 'zh' && item.labelZh ? item.labelZh : item.label;
@@ -31,6 +31,18 @@ const switchUrl = locale === 'zh' ? path : `/zh${path}`;
       ))}
       <a href="https://github.com/apache/apisix"; title="GitHub">GitHub</a>
       <a href={switchUrl} title={locale === 'zh' ? 'English' : '简体中文'}>{locale 
=== 'zh' ? 'EN' : '中'}</a>
+      {search && <div id="docsearch" class="docsearch-slot" />}
+      {search && (
+        <script
+          src="https://cdn.jsdelivr.net/npm/@docsearch/js@3";
+          data-appid="38VC84A2WJ"
+          data-apikey="73248b6e5908d49bb7986c4aef5fd30d"
+          data-indexname="apache_apisix"
+          is:inline
+          defer
+          
onload="docsearch({container:'#docsearch',appId:this.dataset.appid,apiKey:this.dataset.apikey,indexName:this.dataset.indexname})"
+        />
+      )}
     </nav>
     <details class="mobile-toggle">
       <summary aria-label="Menu">☰</summary>
diff --git a/next/src/layouts/Base.astro b/next/src/layouts/Base.astro
index 82b5544f5d8..8bff41dbd85 100644
--- a/next/src/layouts/Base.astro
+++ b/next/src/layouts/Base.astro
@@ -17,6 +17,8 @@ interface Props {
   /** Open Graph object type; article pages pass "article". */
   ogType?: 'website' | 'article';
   noindex?: boolean;
+  /** Load Algolia DocSearch (docs pages only — every other page stays 
zero-JS). */
+  search?: boolean;
   /** Cross-site canonical embedded in upstream docs (docs.api7.ai hub pages). 
*/
   canonicalOverride?: string;
   /** Set false when the title is already the complete tag (homepage). */
@@ -35,6 +37,7 @@ const {
   canonicalOverride,
   titleSuffix = true,
   ogType = 'website',
+  search = false,
 } = Astro.props;
 
 const enUrl = `${SITE}${path}`;
@@ -68,9 +71,15 @@ const allJsonLd = [...ORG_JSONLD, ...jsonLd];
   <meta name="twitter:card" content={image ? 'summary_large_image' : 
'summary'} />
   <meta name="twitter:site" content="@apacheapisix" />
   <script type="application/ld+json" set:html={JSON.stringify(allJsonLd)} />
+  {search && (
+    <>
+      <link rel="preconnect" href="https://38VC84A2WJ-dsn.algolia.net"; 
crossorigin />
+      <link rel="stylesheet" 
href="https://cdn.jsdelivr.net/npm/@docsearch/css@3"; />
+    </>
+  )}
 </head>
 <body>
-  <Header locale={locale} path={path} />
+  <Header locale={locale} path={path} search={search} />
   <main>
     <slot />
   </main>
diff --git a/next/src/layouts/DocPage.astro b/next/src/layouts/DocPage.astro
index 3d07ba5cf4a..4be638d6b3a 100644
--- a/next/src/layouts/DocPage.astro
+++ b/next/src/layouts/DocPage.astro
@@ -37,6 +37,7 @@ const renderable = (node: SidebarNode): boolean => !!node.id 
|| !!(node.items &&
   path={path}
   jsonLd={jsonLd}
   canonicalOverride={entry.mod.frontmatter.canonical}
+  search
 >
   <div class="container docs-layout">
     {sidebar.length > 0 && (
diff --git a/next/src/styles/global.css b/next/src/styles/global.css
index 00a1dff941d..0dd5d93fdbf 100644
--- a/next/src/styles/global.css
+++ b/next/src/styles/global.css
@@ -428,3 +428,10 @@ section.endcta.alt { background: transparent; padding: 
4rem 1rem 0; }
 .anchor-link { opacity: 0; margin-left: .35rem; font-size: .8em; }
 h2:hover .anchor-link, h3:hover .anchor-link, .anchor-link:focus-visible { 
opacity: 1; }
 @media (hover: none) { .anchor-link { opacity: .55; } }
+
+/* ---------- docs search (Algolia DocSearch, docs pages only) ---------- */
+.docsearch-slot { margin-left: .5rem; }
+.docsearch-slot .DocSearch-Button { height: 36px; margin: 0; border-radius: 
8px; background: var(--color-surface-alt); }
+.docsearch-slot .DocSearch-Button:hover { box-shadow: none; border-color: 
var(--color-hover-border); }
+:root { --docsearch-primary-color: var(--color-primary); 
--docsearch-highlight-color: var(--color-primary); }
+@media (max-width: 996px) { .docsearch-slot { display: none; } }

Reply via email to