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

Yilialinn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-website.git


The following commit(s) were added to refs/heads/master by this push:
     new 5ab656365a0 feat(seo): technical SEO fixes (hreflang dedup, Plugin 
Hub, structured data, blog H1, Netlify noindex) (#2054)
5ab656365a0 is described below

commit 5ab656365a0b6cdb4279c41c24b40a080b7772e0
Author: Ming Wen <[email protected]>
AuthorDate: Tue Jun 23 10:53:54 2026 +0800

    feat(seo): technical SEO fixes (hreflang dedup, Plugin Hub, structured 
data, blog H1, Netlify noindex) (#2054)
---
 blog/src/theme/BlogListPage/index.tsx |  4 ++
 config/hreflang.js                    | 82 -----------------------------------
 netlify.toml                          | 13 ++++++
 website/docusaurus.config.js          |  1 -
 website/src/pages/index.tsx           | 64 +++++++++++++++++++++++++++
 website/src/pages/plugins.tsx         | 65 +++++++++++++++++++++++++--
 6 files changed, 143 insertions(+), 86 deletions(-)

diff --git a/blog/src/theme/BlogListPage/index.tsx 
b/blog/src/theme/BlogListPage/index.tsx
index 531ae9cf479..6b850461ec1 100644
--- a/blog/src/theme/BlogListPage/index.tsx
+++ b/blog/src/theme/BlogListPage/index.tsx
@@ -22,6 +22,9 @@ const BlogListPage: FC<Props> = (props) => {
   const { blogDescription, blogTitle, permalink } = metadata;
   const isBlogOnlyMode = permalink === '/';
   const title = isBlogOnlyMode ? siteTitle : blogTitle;
+  // Visible H1 for the blog list page. The default Docusaurus blog index ships
+  // without one, which is an on-page SEO gap (every indexable page needs an 
H1).
+  const headingTitle = isBlogOnlyMode ? siteTitle : `${siteTitle} 
${blogTitle}`;
 
   return (
     <BlogLayout
@@ -34,6 +37,7 @@ const BlogListPage: FC<Props> = (props) => {
       sidebar={sidebar}
       toc={false}
     >
+      <h1 className="margin-bottom--lg">{headingTitle}</h1>
       <BlogPosts
         itemType="http://schema.org/Blog";
         items={items}
diff --git a/config/hreflang.js b/config/hreflang.js
deleted file mode 100644
index a0ef7d88dd0..00000000000
--- a/config/hreflang.js
+++ /dev/null
@@ -1,82 +0,0 @@
-const SITE_URL = 'https://apisix.apache.org';
-
-/**
- * Docusaurus plugin that injects hreflang <link> tags and canonical URLs
- * into every HTML page during the post-build phase.
- *
- * For each page, it generates:
- *   <link rel="alternate" hreflang="en"        
href="https://apisix.apache.org/..."; />
- *   <link rel="alternate" hreflang="zh"        
href="https://apisix.apache.org/zh/..."; />
- *   <link rel="alternate" hreflang="x-default" 
href="https://apisix.apache.org/..."; />
- *   <link rel="canonical" href="..." />  (if not already present)
- */
-module.exports = function hreflangPlugin() {
-  return {
-    name: 'hreflang',
-
-    async postBuild({ outDir }) {
-      const fs = require('fs');
-      const path = require('path');
-
-      function findHtmlFiles(dir) {
-        const results = [];
-        const entries = fs.readdirSync(dir, { withFileTypes: true });
-        for (const entry of entries) {
-          const fullPath = path.join(dir, entry.name);
-          if (entry.isDirectory()) {
-            results.push(...findHtmlFiles(fullPath));
-          } else if (entry.name.endsWith('.html')) {
-            results.push(fullPath);
-          }
-        }
-        return results;
-      }
-
-      const htmlFiles = findHtmlFiles(outDir);
-
-      for (const filePath of htmlFiles) {
-        let html = fs.readFileSync(filePath, 'utf-8');
-
-        // Determine relative path from outDir
-        const relativePath = path.relative(outDir, filePath);
-
-        // Determine the current locale based on file path
-        const isZh = relativePath.startsWith('zh' + path.sep);
-
-        // Compute the path without locale prefix
-        const pathWithoutLocale = isZh
-          ? relativePath.slice(3) // remove "zh/"
-          : relativePath;
-
-        // Normalize path separators for URL
-        const urlPath = pathWithoutLocale.split(path.sep).join('/');
-
-        // Build URLs
-        const enUrl = `${SITE_URL}/${urlPath}`.replace(/\/index\.html$/, '/');
-        const zhUrl = `${SITE_URL}/zh/${urlPath}`.replace(/\/index\.html$/, 
'/');
-        const currentUrl = isZh ? zhUrl : enUrl;
-
-        // Build hreflang tags
-        const hreflangTags = [
-          `<link rel="alternate" hreflang="en" href="${enUrl}" />`,
-          `<link rel="alternate" hreflang="zh" href="${zhUrl}" />`,
-          `<link rel="alternate" hreflang="x-default" href="${enUrl}" />`,
-        ].join('\n    ');
-
-        // Add canonical tag if not already present
-        const hasCanonical = html.includes('rel="canonical"');
-        const canonicalTag = hasCanonical
-          ? ''
-          : `<link rel="canonical" href="${currentUrl}" />`;
-
-        // Inject before </head>
-        const injection = [hreflangTags, canonicalTag]
-          .filter(Boolean)
-          .join('\n    ');
-        html = html.replace('</head>', `    ${injection}\n  </head>`);
-
-        fs.writeFileSync(filePath, html, 'utf-8');
-      }
-    },
-  };
-};
diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 00000000000..6ea98ed6b60
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,13 @@
+# The production site (https://apisix.apache.org) is served by Apache
+# infrastructure (Apache httpd + Fastly) and does NOT read this file.
+#
+# This config exists only to keep the auxiliary Netlify deploy
+# (apache-apisix.netlify.app and PR deploy previews) OUT of search indexes, so
+# they don't compete with the canonical production site for rankings. Netlify
+# applies these headers to every deploy it builds from this repo; Apache infra
+# ignores the file entirely.
+[[headers]]
+  for = "/*"
+
+  [headers.values]
+    X-Robots-Tag = "noindex"
diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js
index 0cb370dfac7..ec15903c283 100644
--- a/website/docusaurus.config.js
+++ b/website/docusaurus.config.js
@@ -109,7 +109,6 @@ module.exports = {
     ],
     ['docusaurus-plugin-sass', {}],
     require.resolve('../config/schema-org'),
-    require.resolve('../config/hreflang'),
   ],
   themeConfig: {
     navbar: {
diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx
index 97212bb9bdd..a57c738c3c3 100644
--- a/website/src/pages/index.tsx
+++ b/website/src/pages/index.tsx
@@ -14,6 +14,68 @@ import Comparison from '../components/sections/Comparison';
 import OpensourcePromo from '../components/sections/OpensourcePromo';
 import EndCTA from '../components/sections/Endcta';
 
+// Structured data for the homepage. Organization + WebSite are already 
injected
+// globally by config/schema-org.js; these add product-level 
(SoftwareApplication)
+// and FAQ markup to improve rich results and AI-answer eligibility.
+const SOFTWARE_APPLICATION_SCHEMA = {
+  '@context': 'https://schema.org',
+  '@type': 'SoftwareApplication',
+  name: 'Apache APISIX',
+  applicationCategory: 'DeveloperApplication',
+  applicationSubCategory: 'API Gateway',
+  operatingSystem: 'Linux, Docker, Kubernetes',
+  description:
+    'Apache APISIX is a dynamic, high-performance, open-source API gateway and 
AI gateway with load balancing, authentication, rate limiting, observability, 
and 100+ plugins.',
+  url: 'https://apisix.apache.org/',
+  license: 'https://www.apache.org/licenses/LICENSE-2.0',
+  isAccessibleForFree: true,
+  offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
+  author: {
+    '@type': 'Organization',
+    name: 'The Apache Software Foundation',
+    url: 'https://www.apache.org/',
+  },
+};
+
+const HOMEPAGE_FAQ_SCHEMA = {
+  '@context': 'https://schema.org',
+  '@type': 'FAQPage',
+  mainEntity: [
+    {
+      '@type': 'Question',
+      name: 'What is Apache APISIX?',
+      acceptedAnswer: {
+        '@type': 'Answer',
+        text: 'Apache APISIX is an open-source, high-performance API gateway 
and AI gateway. It is a top-level project of the Apache Software Foundation and 
provides load balancing, authentication, rate limiting, observability, and 100+ 
plugins for managing API and LLM traffic at scale.',
+      },
+    },
+    {
+      '@type': 'Question',
+      name: 'Is Apache APISIX free and open source?',
+      acceptedAnswer: {
+        '@type': 'Answer',
+        text: 'Yes. Apache APISIX is licensed under the Apache License 2.0 and 
is completely free and open source, with no open-core paywall — all plugins and 
features are available in the open-source project.',
+      },
+    },
+    {
+      '@type': 'Question',
+      name: 'What is the difference between an API gateway and an AI gateway?',
+      acceptedAnswer: {
+        '@type': 'Answer',
+        text: 'An API gateway manages traffic for general APIs, while an AI 
gateway adds capabilities optimized for LLM workloads such as multi-provider AI 
proxying, LLM load balancing, token-based rate limiting, retries and fallback, 
and prompt security. Apache APISIX provides both.',
+      },
+    },
+    {
+      '@type': 'Question',
+      name: 'Does Apache APISIX support Kubernetes?',
+      acceptedAnswer: {
+        '@type': 'Answer',
+        text: 'Yes. Apache APISIX runs on Kubernetes and provides an Ingress 
Controller, along with Helm charts and a Docker image for cloud-native 
deployments.',
+      },
+    },
+  ],
+};
+
 const ThemeResetComponent = () => {
   const { isDarkTheme, setLightTheme } = useThemeContext();
   const windowType = useWindowType();
@@ -70,6 +132,8 @@ const Index: FC = () => (
         name="twitter:description"
         content="Apache APISIX is a dynamic, high-performance, open-source API 
gateway and AI gateway. Features include load balancing, authentication, rate 
limiting, AI proxying, LLM load balancing, and 100+ plugins."
       />
+      <script 
type="application/ld+json">{JSON.stringify(SOFTWARE_APPLICATION_SCHEMA)}</script>
+      <script 
type="application/ld+json">{JSON.stringify(HOMEPAGE_FAQ_SCHEMA)}</script>
     </Head>
     <HeroSection />
     <Architecture />
diff --git a/website/src/pages/plugins.tsx b/website/src/pages/plugins.tsx
index 0b64fc95805..594d1a51f66 100644
--- a/website/src/pages/plugins.tsx
+++ b/website/src/pages/plugins.tsx
@@ -233,12 +233,71 @@ const Plugins: FC = () => {
     );
   });
 
+  // Structured data for the plugin catalog: a CollectionPage whose ItemList
+  // enumerates every plugin, plus an FAQPage to surface in rich results / AI 
answers.
+  const allPlugins = plugins.flatMap((section) => section.plugins);
+  const collectionSchema = {
+    '@context': 'https://schema.org',
+    '@type': 'CollectionPage',
+    name: 'Apache APISIX Plugin Hub',
+    description:
+      'Browse 100+ Apache APISIX plugins for authentication, security, traffic 
control, observability, serverless, and AI.',
+    url: 'https://apisix.apache.org/plugins/',
+    isPartOf: { '@type': 'WebSite', name: 'Apache APISIX', url: 
'https://apisix.apache.org' },
+    mainEntity: {
+      '@type': 'ItemList',
+      numberOfItems: allPlugins.length,
+      itemListElement: allPlugins.map((plugin, index) => {
+        const slug = plugin.name.indexOf('serverless') !== -1 ? 'serverless' : 
plugin.name;
+        const basePath = plugin.beta ? 'next/plugins' : 'plugins';
+        return {
+          '@type': 'ListItem',
+          position: index + 1,
+          name: plugin.name,
+          url: `https://apisix.apache.org/docs/apisix/${basePath}/${slug}/`,
+        };
+      }),
+    },
+  };
+  const faqSchema = {
+    '@context': 'https://schema.org',
+    '@type': 'FAQPage',
+    mainEntity: [
+      {
+        '@type': 'Question',
+        name: 'How many plugins does Apache APISIX have?',
+        acceptedAnswer: {
+          '@type': 'Answer',
+          text: 'Apache APISIX ships with 100+ plugins covering 
authentication, security, traffic control, observability, serverless, and AI 
gateway use cases, all available in the open-source project.',
+        },
+      },
+      {
+        '@type': 'Question',
+        name: 'What can Apache APISIX plugins do?',
+        acceptedAnswer: {
+          '@type': 'Answer',
+          text: 'APISIX plugins extend the gateway with capabilities such as 
key, JWT and OpenID Connect authentication, rate limiting, CORS, request and 
response transformation, Prometheus and OpenTelemetry observability, and AI 
proxying for LLM providers.',
+        },
+      },
+      {
+        '@type': 'Question',
+        name: 'Can I write custom plugins for Apache APISIX?',
+        acceptedAnswer: {
+          '@type': 'Answer',
+          text: 'Yes. You can write custom plugins in Lua, or use the Java, 
Go, and Python plugin runners and Wasm to develop plugins in other languages.',
+        },
+      },
+    ],
+  };
+
   return (
-    <Layout title={translate({ message: 'Plugin Hub' })}>
+    <Layout title={translate({ id: 'plugins.meta.title', message: 'Plugin Hub: 
100+ API Gateway & AI Plugins' })}>
       <Head>
-        <meta name="description" content={translate({ id: 
'plugins.meta.description', message: 'Explore 80+ Apache APISIX plugins for 
authentication, security, traffic control, observability, and AI. Powerful 
integrations for your API Gateway.' })} />
-        <meta property="og:description" content={translate({ id: 
'plugins.meta.ogDescription', message: 'Explore 80+ Apache APISIX plugins for 
authentication, security, traffic control, observability, and AI.' })} />
+        <meta name="description" content={translate({ id: 
'plugins.meta.description', message: 'Explore 100+ Apache APISIX plugins for 
authentication, security, traffic control, observability, and AI. Powerful 
integrations for your API Gateway.' })} />
+        <meta property="og:description" content={translate({ id: 
'plugins.meta.ogDescription', message: 'Explore 100+ Apache APISIX plugins for 
authentication, security, traffic control, observability, and AI.' })} />
         <script src="/js/plugin-icon.js" defer />
+        <script 
type="application/ld+json">{JSON.stringify(collectionSchema)}</script>
+        <script type="application/ld+json">{JSON.stringify(faqSchema)}</script>
       </Head>
       <Page>
         <PageTitle>

Reply via email to