This is an automated email from the ASF dual-hosted git repository. moonming pushed a commit to branch feat/astro-wave2 in repository https://gitbox.apache.org/repos/asf/apisix-website.git
commit b8f16f193e4fa0e985c88da28ded7aff66e07558 Author: Ming Wen <[email protected]> AuthorDate: Thu Jul 23 11:52:18 2026 +0800 feat: serve blog, learning center, and articles from the static build (wave 2) Second wave of the incremental migration in next/MIGRATION.md. The /blog/ (EN+ZH), /learning-center/, /articles/, and /events/archive/ subtrees are now produced by the zero-JS Astro build; every other URL is still produced by Docusaurus. Deploy pipeline: - the Astro step syncs blog/learning-center/articles markdown from this very checkout (WEBSITE_REPO env) before building — no external fetches - a parity gate fails the deploy if any URL Docusaurus produced under a migrated prefix is missing from the Astro build - the overlay allowlist extends to the wave-2 subtrees; Docusaurus' RSS/atom feeds for learning-center/articles are carried into the tree so feed URLs stay alive Astro pages: - blog tag pages (10 EN + 6 ZH + index) and per-post tag links, matching the production URL set exactly - article pages emit og:type=article like production Old homepage removal: - the React homepage and its section components leave the website workspace (the / route is produced by Astro since wave 1; keeping it in the bundle showed the stale page on client-side navigations) - all four workspaces' navbar logos use pathname:/// for a full-page navigation to the static homepage - update-sitemap-loc.js re-adds the / and /zh/ entries the website workspace's sitemap no longer contains Verified against the current asf-site tree: index.html count unchanged (4306), wave-2 subtree URL sets byte-exact vs production (0 missing, 0 extra), 8/8 feeds byte-identical, all 8681 files outside the allowlist untouched, sitemap.xml untouched, head parity on sampled posts (canonical/title/og:type/JSON-LD/hreflang). NOT for direct merge: needs the usual reviews. --- .github/workflows/deploy.yml | 74 ++- blog/en/docusaurus.config.js | 2 + blog/zh/docusaurus.config.js | 2 + doc/docusaurus.config.js | 2 + next/scripts/sync-content.mjs | 7 +- next/src/layouts/Article.astro | 2 +- next/src/layouts/Base.astro | 5 +- next/src/pages/blog/[...rest].astro | 2 +- next/src/pages/blog/tags/[tag].astro | 21 + next/src/pages/blog/tags/index.astro | 19 + next/src/pages/zh/blog/[...rest].astro | 2 +- next/src/pages/zh/blog/tags/[tag].astro | 21 + next/src/pages/zh/blog/tags/index.astro | 19 + scripts/update-sitemap-loc.js | 19 + website/docusaurus.config.js | 4 + website/src/components/ArrowAnim.tsx | 27 - .../src/components/sections/AIGatewaySection.tsx | 123 ----- website/src/components/sections/Architecture.tsx | 56 --- website/src/components/sections/Benefits.tsx | 550 --------------------- website/src/components/sections/Comparison.tsx | 89 ---- website/src/components/sections/Endcta.tsx | 40 -- website/src/components/sections/Features.tsx | 28 -- website/src/components/sections/HeroSection.tsx | 50 -- .../src/components/sections/HomeEventsSection.tsx | 74 --- website/src/components/sections/Integrations.tsx | 127 ----- .../src/components/sections/OpensourcePromo.tsx | 90 ---- website/src/components/sections/Pathways.tsx | 67 --- .../src/css/landing-sections/ai-gateway-home.scss | 157 ------ website/src/css/landing-sections/architecture.scss | 114 ----- website/src/css/landing-sections/benefits.scss | 39 -- website/src/css/landing-sections/comparison.scss | 95 ---- .../src/css/landing-sections/endcta.module.scss | 71 --- .../src/css/landing-sections/features.module.scss | 322 ------------ website/src/css/landing-sections/hero.scss | 249 ---------- .../css/landing-sections/home-events.module.scss | 146 ------ website/src/css/landing-sections/integrations.scss | 89 ---- .../src/css/landing-sections/os-promo.module.scss | 53 -- website/src/css/landing-sections/pathways.scss | 62 --- website/src/pages/index.tsx | 154 ------ 39 files changed, 180 insertions(+), 2893 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4b09e6b13af..a4180a7d141 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -149,32 +149,69 @@ jobs: run: | yarn update-sitemap && git status - # ---- Static homepage migration (wave 1) -------------------------------- - # The landing page (/ and /zh/) is served from a zero-JS Astro build in - # next/; every other URL above is produced by Docusaurus. The Astro - # homepage needs no synced docs, so this build skips the content sync. - # Runs last, after every Node 16 Docusaurus step (build, canonical - # assert, sitemap), so switching to Node 22 never touches the Docusaurus - # toolchain. See next/MIGRATION.md. + # ---- Static migration (waves 1-2) -------------------------------------- + # Wave 1: the landing pages (/ and /zh/). Wave 2: the content subtrees + # whose markdown lives in THIS repo — blog (EN+ZH), learning-center, + # articles, events/archive. Everything else is still produced by + # Docusaurus. Runs last, after every Node 16 Docusaurus step (build, + # canonical assert, sitemap), so switching to Node 22 never touches the + # Docusaurus toolchain. See next/MIGRATION.md. - name: Set up Node.js 22 for the Astro build uses: actions/setup-node@v4 with: node-version: '22' - - name: Build the Astro homepage + - name: Build the Astro pages working-directory: next run: | npm ci + # Blog/learning-center/articles markdown is synced from this very + # checkout (WEBSITE_REPO); project docs stay un-synced until wave 3. + WEBSITE_REPO="$GITHUB_WORKSPACE" node scripts/sync-content.mjs npx astro build - - name: Overlay the Astro homepage onto the Docusaurus build + - name: Assert URL parity for the wave-2 subtrees run: | set -euo pipefail - # File-level allowlist: only the two landing pages and the shared - # Astro asset dir come from the Astro build. Everything else in - # website/build (docs, blog, sitemaps, .htaccess, robots) is left as - # Docusaurus produced it, so URLs stay byte-identical. - for path in index.html zh/index.html _astro; do + # Every URL Docusaurus produced under a migrated prefix must exist in + # the Astro build too (same index.html path). Astro may add pages + # (blog tag pages are deliberately additive); it must never drop one. + ROOT="$PWD" + fail=0 + for prefix in blog zh/blog learning-center zh/learning-center articles zh/articles events zh/events; do + [ -d "website/build/$prefix" ] || continue + missing=$(cd "website/build/$prefix" && find . -name index.html | sort | while read -r f; do + [ -f "$ROOT/next/dist/$prefix/$f" ] || echo "$prefix/$f" + done) + if [ -n "$missing" ]; then + echo "MISSING in Astro build:"; echo "$missing"; fail=1 + fi + done + exit $fail + + - name: Carry the Docusaurus RSS/atom feeds into the Astro tree + run: | + set -euo pipefail + # Docusaurus generates subscription feeds for these sections; the + # Astro build does not (yet). Copy them into the Astro tree so the + # subtree replacement below keeps the feed URLs alive. + for f in learning-center/atom.xml learning-center/rss.xml \ + articles/atom.xml articles/rss.xml \ + zh/learning-center/atom.xml zh/learning-center/rss.xml \ + zh/articles/atom.xml zh/articles/rss.xml; do + cp "website/build/$f" "next/dist/$f" + done + + - name: Overlay the Astro pages onto the Docusaurus build + run: | + set -euo pipefail + # Path allowlist: the landing pages, the wave-2 content subtrees, and + # the shared Astro asset dir come from the Astro build. Everything + # else in website/build (docs, plugins, sitemaps, .htaccess, robots) + # is left as Docusaurus produced it, so those URLs stay byte-identical. + for path in index.html zh/index.html _astro \ + blog zh/blog learning-center zh/learning-center \ + articles zh/articles events zh/events; do rm -rf "website/build/$path" mkdir -p "website/build/$(dirname "$path")" cp -R "next/dist/$path" "website/build/$path" @@ -185,6 +222,15 @@ jobs: grep -q '同一个网关' website/build/zh/index.html grep -q '/_astro/.*\.css' website/build/index.html test -d website/build/_astro + # Wave-2 sanity: key subtree landmarks exist. + test -f website/build/blog/index.html + test -f website/build/zh/blog/index.html + test -f website/build/blog/tags/index.html + test -f website/build/learning-center/index.html + test -f website/build/learning-center/atom.xml + test -f website/build/articles/index.html + test -f website/build/zh/articles/rss.xml + test -f website/build/events/archive/index.html # Merge the homepage's image assets (integration brand icons, feature # infographics, architecture diagram) into the Docusaurus img tree. # cp merges: it adds the new files and overwrites only same-named diff --git a/blog/en/docusaurus.config.js b/blog/en/docusaurus.config.js index 93003060185..8052d4dccd8 100644 --- a/blog/en/docusaurus.config.js +++ b/blog/en/docusaurus.config.js @@ -83,6 +83,8 @@ module.exports = { title: 'Apache APISIX®', logo: { src: 'img/logo2.svg', + // Full-page navigation: / is produced by the static Astro build. + href: 'pathname:///', }, items: require('../../config/navbar.js'), }, diff --git a/blog/zh/docusaurus.config.js b/blog/zh/docusaurus.config.js index 23d6b7b915b..e5880043e33 100644 --- a/blog/zh/docusaurus.config.js +++ b/blog/zh/docusaurus.config.js @@ -85,6 +85,8 @@ module.exports = { title: 'Apache APISIX®', logo: { src: 'img/logo2.svg', + // Full-page navigation: / is produced by the static Astro build. + href: 'pathname:///', }, items: require('../../config/navbar.js'), }, diff --git a/doc/docusaurus.config.js b/doc/docusaurus.config.js index ef8c7e8d008..565144d0fce 100644 --- a/doc/docusaurus.config.js +++ b/doc/docusaurus.config.js @@ -213,6 +213,8 @@ module.exports = { title: 'Apache APISIX®', logo: { src: 'img/logo2.svg', + // Full-page navigation: / is produced by the static Astro build. + href: 'pathname:///', }, items: require('../config/navbar.js'), }, diff --git a/next/scripts/sync-content.mjs b/next/scripts/sync-content.mjs index 258b7c7a6a9..d14b7dcf354 100644 --- a/next/scripts/sync-content.mjs +++ b/next/scripts/sync-content.mjs @@ -16,9 +16,10 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; const root = path.dirname(path.dirname(fileURLToPath(import.meta.url))); -// Clean export of apache/apisix-website origin/master (populated via -// `git archive`), decoupled from the mutable working checkout next door. -const WEBSITE_REPO = path.join(root, '.sync/website'); +// Clean export of apache/apisix-website (populated via `git archive`), +// decoupled from any mutable working checkout. In CI, WEBSITE_REPO points at +// the checked-out repo root itself. +const WEBSITE_REPO = process.env.WEBSITE_REPO || path.join(root, '.sync/website'); const OUT = path.join(root, 'content'); const stats = { copied: 0, tabsFlattened: 0, importsStripped: 0, codeTitles: 0, canonicals: 0 }; diff --git a/next/src/layouts/Article.astro b/next/src/layouts/Article.astro index 10d123a81b2..b14562fb60c 100644 --- a/next/src/layouts/Article.astro +++ b/next/src/layouts/Article.astro @@ -27,7 +27,7 @@ const jsonLd = [{ mainEntityOfPage: `${SITE}${locale === 'zh' ? '/zh' : ''}${path}`, }]; --- -<Base title={post.title} description={post.description} locale={locale} path={path} image={post.image} jsonLd={jsonLd}> +<Base title={post.title} description={post.description} locale={locale} path={path} image={post.image} jsonLd={jsonLd} ogType="article"> <div class="container article-wrap"> <article class="prose"> <h1>{post.title}</h1> diff --git a/next/src/layouts/Base.astro b/next/src/layouts/Base.astro index 553a8d0b2bd..82b5544f5d8 100644 --- a/next/src/layouts/Base.astro +++ b/next/src/layouts/Base.astro @@ -14,6 +14,8 @@ interface Props { jsonLd?: object[]; /** Set false for pages that exist in only one locale. */ hasAlternate?: boolean; + /** Open Graph object type; article pages pass "article". */ + ogType?: 'website' | 'article'; noindex?: boolean; /** Cross-site canonical embedded in upstream docs (docs.api7.ai hub pages). */ canonicalOverride?: string; @@ -32,6 +34,7 @@ const { noindex = false, canonicalOverride, titleSuffix = true, + ogType = 'website', } = Astro.props; const enUrl = `${SITE}${path}`; @@ -55,7 +58,7 @@ const allJsonLd = [...ORG_JSONLD, ...jsonLd]; {hasAlternate && <link rel="alternate" hreflang="x-default" href={enUrl} />} <link rel="icon" href={FAVICON} /> <link rel="sitemap" type="application/xml" href={locale === 'zh' ? '/zh/sitemap.xml' : '/sitemap.xml'} /> - <meta property="og:type" content="website" /> + <meta property="og:type" content={ogType} /> <meta property="og:title" content={fullTitle} /> <meta property="og:description" content={desc} /> <meta property="og:url" content={canonical} /> diff --git a/next/src/pages/blog/[...rest].astro b/next/src/pages/blog/[...rest].astro index 24baea576df..a4b224eba38 100644 --- a/next/src/pages/blog/[...rest].astro +++ b/next/src/pages/blog/[...rest].astro @@ -10,4 +10,4 @@ export function getStaticPaths() { } const { post } = Astro.props; --- -<Article post={post} locale="en" path={post.url} schemaType="BlogPosting" /> +<Article post={post} locale="en" path={post.url} tagBase="/blog" schemaType="BlogPosting" /> diff --git a/next/src/pages/blog/tags/[tag].astro b/next/src/pages/blog/tags/[tag].astro new file mode 100644 index 00000000000..626ebae3869 --- /dev/null +++ b/next/src/pages/blog/tags/[tag].astro @@ -0,0 +1,21 @@ +--- +import ListPage from '../../../layouts/ListPage.astro'; +import { getBlogPosts, groupByTag } from '../../../lib/content'; + +export function getStaticPaths() { + const tags = groupByTag(getBlogPosts('en')); + return [...tags.entries()].map(([slug, group]) => ({ params: { tag: slug }, props: group })); +} +const { label, posts } = Astro.props; +const tag = Astro.params.tag; +--- +<ListPage + title={`${label} | Blog`} + heading={`Tag: ${label}`} + locale="en" + path={`/blog/tags/${tag}/`} + base={`/blog/tags/${tag}/`} + posts={posts} + current={1} + total={1} +/> diff --git a/next/src/pages/blog/tags/index.astro b/next/src/pages/blog/tags/index.astro new file mode 100644 index 00000000000..b79411a4e61 --- /dev/null +++ b/next/src/pages/blog/tags/index.astro @@ -0,0 +1,19 @@ +--- +import Base from '../../../layouts/Base.astro'; +import { getBlogPosts, groupByTag } from '../../../lib/content'; + +const tags = groupByTag(getBlogPosts('en')); +const sorted = [...tags.entries()].sort((a, b) => a[0].localeCompare(b[0])); +--- +<Base title="Tags | Blog" locale="en" path="/blog/tags/"> + <div class="container"> + <div class="page-title"><h1>Blog tags</h1></div> + <div class="archive-list"> + <ul> + {sorted.map(([slug, { label, posts }]) => ( + <li><a href={`/blog/tags/${slug}/`}>{label}</a> ({posts.length})</li> + ))} + </ul> + </div> + </div> +</Base> diff --git a/next/src/pages/zh/blog/[...rest].astro b/next/src/pages/zh/blog/[...rest].astro index 106ef8d2c65..fb02d06f972 100644 --- a/next/src/pages/zh/blog/[...rest].astro +++ b/next/src/pages/zh/blog/[...rest].astro @@ -11,4 +11,4 @@ export function getStaticPaths() { const { post } = Astro.props; const path = post.url.replace(/^\/zh/, ''); --- -<Article post={post} locale="zh" path={path} schemaType="BlogPosting" /> +<Article post={post} locale="zh" path={path} tagBase="/zh/blog" schemaType="BlogPosting" /> diff --git a/next/src/pages/zh/blog/tags/[tag].astro b/next/src/pages/zh/blog/tags/[tag].astro new file mode 100644 index 00000000000..446531af817 --- /dev/null +++ b/next/src/pages/zh/blog/tags/[tag].astro @@ -0,0 +1,21 @@ +--- +import ListPage from '../../../../layouts/ListPage.astro'; +import { getBlogPosts, groupByTag } from '../../../../lib/content'; + +export function getStaticPaths() { + const tags = groupByTag(getBlogPosts('zh')); + return [...tags.entries()].map(([slug, group]) => ({ params: { tag: slug }, props: group })); +} +const { label, posts } = Astro.props; +const tag = Astro.params.tag; +--- +<ListPage + title={`${label} | 博客`} + heading={`标签:${label}`} + locale="zh" + path={`/blog/tags/${tag}/`} + base={`/zh/blog/tags/${tag}/`} + posts={posts} + current={1} + total={1} +/> diff --git a/next/src/pages/zh/blog/tags/index.astro b/next/src/pages/zh/blog/tags/index.astro new file mode 100644 index 00000000000..0051b9167b4 --- /dev/null +++ b/next/src/pages/zh/blog/tags/index.astro @@ -0,0 +1,19 @@ +--- +import Base from '../../../../layouts/Base.astro'; +import { getBlogPosts, groupByTag } from '../../../../lib/content'; + +const tags = groupByTag(getBlogPosts('zh')); +const sorted = [...tags.entries()].sort((a, b) => a[0].localeCompare(b[0])); +--- +<Base title="标签 | 博客" locale="zh" path="/blog/tags/"> + <div class="container"> + <div class="page-title"><h1>博客标签</h1></div> + <div class="archive-list"> + <ul> + {sorted.map(([slug, { label, posts }]) => ( + <li><a href={`/zh/blog/tags/${slug}/`}>{label}</a> ({posts.length})</li> + ))} + </ul> + </div> + </div> +</Base> diff --git a/scripts/update-sitemap-loc.js b/scripts/update-sitemap-loc.js index b4c772238bc..00f9f847249 100644 --- a/scripts/update-sitemap-loc.js +++ b/scripts/update-sitemap-loc.js @@ -111,6 +111,22 @@ function filterSitemapUrls(sitemap) { return before - sitemap.urlset.url.length; } +/** + * Ensure the homepage entry exists. The landing page is produced by the + * static Astro build (next/), not by the website workspace, so the merged + * Docusaurus sitemaps no longer contain it. + */ +function ensureHomepage(sitemap, url) { + const urls = sitemap.urlset.url; + if (urls.some((entry) => entry.loc && entry.loc._text === url)) return; + urls.unshift({ + loc: { _text: url }, + lastmod: { _text: new Date().toISOString().split('T')[0] }, + priority: { _text: getPriority(url) }, + changefreq: { _text: getChangefreq(url) }, + }); +} + const tasks = new Listr([ { title: `Check sitemap.xml files exist`, @@ -140,6 +156,9 @@ const tasks = new Listr([ } const removed = filterSitemapUrls(res); console.log(` Filtered out ${removed} URLs from ${group[0]}`); + ensureHomepage(res, group[0].includes('/zh/') + ? 'https://apisix.apache.org/zh/' + : 'https://apisix.apache.org/'); return res; }) .then((sitemap) => writeFile(group[0], js2xml(sitemap, { compact: true }, 'utf-8'))), diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 8061dc83066..e9915ed804b 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -117,6 +117,10 @@ module.exports = { title: 'Apache APISIX®', logo: { src: 'img/logo2.svg', + // Full-page navigation: the homepage is produced by the static Astro + // build (next/), not by this SPA — a client-side route to / would + // render the removed React homepage. + href: 'pathname:///', }, items: require('../config/navbar.js'), }, diff --git a/website/src/components/ArrowAnim.tsx b/website/src/components/ArrowAnim.tsx deleted file mode 100644 index ca0be2c8a37..00000000000 --- a/website/src/components/ArrowAnim.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import type { FC } from 'react'; -import React from 'react'; -import Link from '@docusaurus/Link'; -import useBaseUrl from '@docusaurus/useBaseUrl'; - -import Translate from '@docusaurus/Translate'; - -const ArrowAnim: FC = () => ( - <Link - target="_blank" - rel="noopener noreferrer" - to={useBaseUrl('/blog/')} - className="btn-docs" - > - <div className="goto"> - <Translate id="arrowAnim.component.link.btn">Read Blog</Translate> - </div> - <div className="arrow"> - <svg width="70%" height="50%" viewBox="0 0 256 256"> - <polygon points="208,128 80,0 52.8,27.2 140.3,128 53,228.9 80,256" /> - <rect x="-256" y="115" width="256" height="30" /> - </svg> - </div> - </Link> -); - -export default ArrowAnim; diff --git a/website/src/components/sections/AIGatewaySection.tsx b/website/src/components/sections/AIGatewaySection.tsx deleted file mode 100644 index 29c250b67b0..00000000000 --- a/website/src/components/sections/AIGatewaySection.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import type { FC } from 'react'; -import React from 'react'; -import Link from '@docusaurus/Link'; -import Translate from '@docusaurus/Translate'; - -import '../../css/landing-sections/ai-gateway-home.scss'; - -interface Capability { - title: JSX.Element; - desc: JSX.Element; - href: string; -} - -const CAPABILITIES: Capability[] = [ - { - title: <Translate id="home.ai.cap.proxy.t">Multi-LLM proxy</Translate>, - desc: ( - <Translate id="home.ai.cap.proxy.d"> - Route to OpenAI, Anthropic, AWS Bedrock, DeepSeek, Ollama and 20+ providers through one endpoint. - </Translate> - ), - href: '/docs/apisix/plugins/ai-proxy/', - }, - { - title: <Translate id="home.ai.cap.lb.t">LLM load balancing</Translate>, - desc: ( - <Translate id="home.ai.cap.lb.d"> - Distribute traffic across providers and models for throughput and resilience. - </Translate> - ), - href: '/docs/apisix/plugins/ai-proxy-multi/', - }, - { - title: <Translate id="home.ai.cap.fallback.t">Retry & fallback</Translate>, - desc: ( - <Translate id="home.ai.cap.fallback.d"> - Fail over to a backup model or provider automatically when one is unavailable. - </Translate> - ), - href: '/docs/apisix/plugins/ai-proxy-multi/', - }, - { - title: <Translate id="home.ai.cap.token.t">Token rate limiting</Translate>, - desc: ( - <Translate id="home.ai.cap.token.d"> - Cap usage and cost with token-based rate limits per consumer. - </Translate> - ), - href: '/docs/apisix/plugins/ai-rate-limiting/', - }, - { - title: <Translate id="home.ai.cap.security.t">Prompt security</Translate>, - desc: ( - <Translate id="home.ai.cap.security.d"> - Guardrails, content moderation, and prompt decoration before requests reach the model. - </Translate> - ), - href: '/docs/apisix/plugins/ai-prompt-guard/', - }, - { - title: <Translate id="home.ai.cap.mcp.t">MCP support</Translate>, - desc: ( - <Translate id="home.ai.cap.mcp.d"> - Expose and govern Model Context Protocol tools through the gateway. - </Translate> - ), - href: '/ai-gateway/', - }, -]; - -const PROVIDERS = ['OpenAI', 'Anthropic', 'AWS Bedrock', 'DeepSeek', 'Ollama']; - -const AIGatewaySection: FC = () => ( - <section className="ai-home"> - <div className="ai-home__inner"> - <span className="ai-home__eyebrow"> - <Translate id="home.ai.eyebrow">AI Gateway</Translate> - </span> - <h2 className="ai-home__title"> - <Translate id="home.ai.title">The same gateway, now for your LLM traffic</Translate> - </h2> - <p className="ai-home__subtitle"> - <Translate id="home.ai.subtitle"> - Proxy, secure, and govern traffic to 20+ LLM providers — with the performance, plugins, - and observability you already run for your APIs. - </Translate> - </p> - - <div className="ai-home__flow"> - <span className="ai-home__node"> - <Translate id="home.ai.flow.apps">AI apps & agents</Translate> - </span> - <span className="ai-home__arrow" aria-hidden="true">→</span> - <span className="ai-home__node ai-home__node--apisix">Apache APISIX</span> - <span className="ai-home__arrow" aria-hidden="true">→</span> - <span className="ai-home__providers"> - {PROVIDERS.map((provider) => ( - <span className="ai-home__provider" key={provider}>{provider}</span> - ))} - <span className="ai-home__provider ai-home__provider--more"> - <Translate id="home.ai.flow.more">+ more</Translate> - </span> - </span> - </div> - - <div className="ai-home__grid"> - {CAPABILITIES.map((capability) => ( - <Link className="ai-home__card" to={capability.href} key={capability.title.props.id} target="_blank" rel="noopener noreferrer"> - <h3 className="ai-home__card-title">{capability.title}</h3> - <p className="ai-home__card-desc">{capability.desc}</p> - </Link> - ))} - </div> - - <Link className="ai-home__cta" to="/ai-gateway/" target="_blank" rel="noopener noreferrer"> - <Translate id="home.ai.cta">Explore the AI Gateway</Translate> - {' →'} - </Link> - </div> - </section> -); - -export default AIGatewaySection; diff --git a/website/src/components/sections/Architecture.tsx b/website/src/components/sections/Architecture.tsx deleted file mode 100644 index 3f3a0695ede..00000000000 --- a/website/src/components/sections/Architecture.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import type { FC } from 'react'; -import React from 'react'; - -import Translate from '@docusaurus/Translate'; - -import HLDesign from '../../assets/images/infographs/Architecture-min.svg'; -import Pattern from '../../assets/images/PatternGrouped-min.svg'; -import '../../css/landing-sections/architecture.scss'; - -const Architecture: FC = () => ( - <div className="arch"> - <Pattern - className="arch-scale-svg" - width="100vw" - style={{ - strokeWidth: '2px', - opacity: '0.25', - strokeDasharray: '10000', - position: 'absolute', - margin: '0 auto', - bottom: 0, - zIndex: -10, - }} - /> - <h3 className="arch-head"> - <Translate id="architecture.component.title.name"> - Building for large-scale, high value systems - </Translate> - </h3> - <div className="arch-subtitle"> - <p> - <Translate id="architecture.component.title.subtitle"> - Apache APISIX provides open source API Gateway to help you manage microservices, - delivering the ultimate performance, security, and scalable platform for all your APIs and - microservices. - </Translate> - </p> - </div> - <div className="arch-card" style={{ position: 'relative' }}> - <div className="hldesign"> - <HLDesign className="hldesign-graphic" /> - </div> - <div className="arch-card-caption"> - <p> - <Translate id="architecture.component.card.caption"> - Apache APISIX is based on NGINX and etcd. Compared with traditional API Gateways, APISIX - has features like dynamic routing and hot-loading plugins, etc. - </Translate> - </p> - </div> - <div className="arch-card-border" /> - </div> - </div> -); - -export default Architecture; diff --git a/website/src/components/sections/Benefits.tsx b/website/src/components/sections/Benefits.tsx deleted file mode 100644 index d89226ceb11..00000000000 --- a/website/src/components/sections/Benefits.tsx +++ /dev/null @@ -1,550 +0,0 @@ -import type { FC } from 'react'; -import React, { useRef, useEffect } from 'react'; -import Link from '@docusaurus/Link'; -import useBaseUrl from '@docusaurus/useBaseUrl'; -import gsap from 'gsap'; -import { ScrollTrigger } from 'gsap/ScrollTrigger'; - -import Translate from '@docusaurus/Translate'; - -import Performance from '../../assets/images/infographs/performance.svg'; -import Security from '../../assets/images/infographs/security.svg'; -import Scale from '../../assets/images/infographs/scale.svg'; -import Dynamic from '../../assets/images/infographs/dynamic.svg'; -import Multiplatform from '../../assets/images/infographs/multiplatform.svg'; -import useWindowSize from '../../hooks/useWindowSize'; - -import '../../css/landing-sections/benefits.scss'; - -const Benefits: FC = () => { - const triggerDiv = useRef(null); - const performance = useRef(null); - const security = useRef(null); - const scale = useRef(null); - const dynamic = useRef(null); - const multiplatform = useRef(null); - const [screenWidth] = useWindowSize(); - - gsap.registerPlugin(ScrollTrigger); - - useEffect(() => { - const tweenTls = []; - const observers = []; - - for (let i = 0; i < 5; i += 1) { - tweenTls.push( - gsap.timeline({ - paused: true, - yoyo: true, - yoyoEase: 'power3.out', - repeat: -1, - defaults: { - yoyo: true, - ease: 'power3.inOut', - yoyoEase: 'power3.out', - }, - }), - ); - } - - const circles = []; - const links = []; - const colors = ['#FE7F80', 'white', 'red']; - const pathColors = ['#FE7F80', 'black']; - - for (let i = 1; i < 28; i += 1) { - circles.push(`.scale_svg__cir${i}`); - links.push(`.scale_svg__n${i}`); - } - - // Performance anim - tweenTls[0] - .fromTo( - '.performance_svg__network', - { - strokeDashoffset: 1000, - stroke: 'black', - }, - { - strokeDashoffset: 0, - duration: 1, - strokeWidth: 5, - stroke: 'orange', - ease: 'power2.in', - yoyoEase: 'power2.out', - repeat: -1, - }, - ) - .fromTo( - '.performance_svg__lightning', - { - fill: 'orange', - }, - { - fill: 'red', - duration: 1, - repeat: -1, - }, - '-=1', - ); - - // Security anim - tweenTls[1].fromTo( - ['.security_svg__malWarn-square', '.security_svg__malConn'], - { - fill: '#FA5252', - }, - { - fill: 'yellow', - duration: 0.5, - repeat: -1, - repeatDelay: 0.1, - }, - ); - for (let i = 1; i < 4; i += 1) { - tweenTls[1].fromTo( - `.security_svg__conn${i}`, - { - strokeWidth: 4, - strokeDasharray: 25, - strokeDashoffset: 200, - }, - { - strokeDashoffset: 0, - duration: 2.5, - repeat: -1, - ease: 'linear', - yoyoEase: 'linear', - }, - ); - } - - // Scaling anim - for (let i = 0; i < 27; i += 1) { - tweenTls[2].fromTo( - circles[i], - { - fill: gsap.utils.random(colors), - }, - { - fill: gsap.utils.random(colors), - duration: 0.3, - repeat: -1, - repeatDelay: 0.1, - }, - ); - tweenTls[2].fromTo( - links[i], - { - stroke: gsap.utils.random(pathColors), - }, - { - stroke: gsap.utils.random(pathColors), - duration: 0.3, - repeat: -1, - repeatDelay: 0.1, - }, - ); - } - - // Dynamic anim - tweenTls[3].repeatDelay(1.5); - tweenTls[3] - .fromTo( - ['.dynamic_svg__rcard'], - { - x: -400, - opacity: 0, - }, - { - opacity: 1, - x: 0, - ease: 'sin.inOut', - duration: 1.5, - }, - ) - .fromTo( - '.dynamic_svg__arrow', - { - opacity: 0, - }, - { - opacity: 1, - ease: 'power3.out', - duration: 0.5, - }, - ) - .fromTo( - '.dynamic_svg__lightning', - { - opacity: 0, - y: 10, - }, - { - opacity: 1, - y: 0, - duration: 1, - ease: 'power2.inOut', - }, - ); - - // Multiplatform anim - for (let i = 1; i < 4; i += 1) { - tweenTls[4].fromTo( - `.multiplatform_svg__server-port${i}`, - { - fill: '#60E0F2', - }, - { - fill: '#ffdc21', - duration: 0.5, - }, - ); - } - - const standloneObserver = new IntersectionObserver(onIntersection, { - root: null, - threshold: 0.4, - }); - - let rot = 0; - const tweenArrow = gsap.fromTo( - '.multiplatform_svg__arrows', - { - rotation: rot, - }, - { - rotation: -360 + rot, - transformOrigin: '50% 50%', - ease: 'power3.inOut', - duration: 3, - repeat: -1, - paused: true, - onComplete: () => { - rot -= 360; - }, - }, - ); - const tweenFloat = gsap.fromTo( - '.multiplatform_svg__lightning', - { - y: -2.5, - }, - { - y: 5, - duration: 1, - ease: 'linear', - repeat: -1, - yoyo: true, - paused: true, - yoyoEase: 'linear', - }, - ); - - function onIntersection(entries) { - entries.forEach((entry) => { - if (entry.isIntersecting) { - tweenArrow.paused(false); - tweenFloat.paused(false); - } else { - tweenArrow.paused(true); - tweenFloat.paused(true); - } - }); - } - - standloneObserver.observe(multiplatform.current); - - const elems = [ - performance.current, - security.current, - scale.current, - dynamic.current, - multiplatform.current, - ]; - for (let i = 0; i < 5; i += 1) { - observers.push( - new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - tweenTls[i].paused(false); - } else { - tweenTls[i].paused(true); - } - }); - }, - { - root: null, - threshold: 0.2, - }, - ), - ); - } - - observers.forEach((it, index) => { - it.observe(elems[index]); - }); - - return () => { - observers.forEach((it) => { - it.disconnect(); - }); - tweenTls.forEach((it) => { - it.pause(0).kill(true); - }); - }; - }, []); - - useEffect(() => { - ScrollTrigger.saveStyles([ - performance.current, - security.current, - scale.current, - dynamic.current, - multiplatform.current, - ]); - - ScrollTrigger.matchMedia({ - '(max-width: 1100px)': () => { - const tl = gsap.timeline({ - defaults: { - ease: 'linear', - }, - scrollTrigger: { - id: 'benefits-scrolltrigger', - trigger: triggerDiv.current, - start: 'top top', - pin: triggerDiv.current, - scrub: 1, - end: '+=500%', - }, - }); - tl.fromTo( - performance.current, - { - opacity: 1, - }, - { - opacity: 0, - }, - ) - .fromTo( - security.current, - { - opacity: 0, - }, - { - opacity: 1, - }, - ) - .to(security.current, { - opacity: 0, - }) - .fromTo( - scale.current, - { - opacity: 0, - }, - { - opacity: 1, - }, - ) - .to(scale.current, { - opacity: 0, - }) - .fromTo( - dynamic.current, - { - opacity: 0, - }, - { - opacity: 1, - }, - ) - .to(dynamic.current, { - opacity: 0, - }) - .fromTo( - multiplatform.current, - { - opacity: 0, - }, - { - opacity: 1, - }, - ); - }, - '(min-width: 1101px)': () => { - const tl = gsap.timeline(); - tl.to(performance.current, { - opacity: 1, - }) - .to(security.current, { - opacity: 1, - }) - .to(scale.current, { - opacity: 1, - }) - .to(dynamic.current, { - opacity: 1, - }) - .to(multiplatform.current, { - opacity: 1, - }); - }, - }); - }, []); - - return ( - <div ref={triggerDiv} className="benefit" style={{ position: 'relative' }}> - <div ref={performance} className="row-benefit"> - <div style={{ width: screenWidth > 768 ? '50%' : '100%' }}> - <h3 className="feat-head-desc"> - <Translate id="benefits.component.performance.title">Performance</Translate> - </h3> - <h2 className="feat-head add-left-margin"> - <Translate id="benefits.component.performance.subtitle">Ultimate performance</Translate> - </h2> - <p className="feat-desc add-left-margin"> - <strong> - <Translate id="benefits.component.performance.message"> - Apache APISIX Gateway uses radixtree-route-matching and etcd under the hood to - provide you the ability to create high speed synchronized systems. From routing to - built-in plugins, all these are designed and implemented to be uber performant with - the minimum latency possible. - </Translate> - </strong> - </p> - </div> - <div className="benefit-infograph"> - <Performance style={{ width: screenWidth >= 768 ? '35%' : '40%' }} /> - </div> - </div> - - <div ref={security} className="row-benefit row-reverse row-hidden"> - <div className="benefit-infograph"> - <Security - style={{ - width: screenWidth >= 768 ? '75%' : '100%', - position: 'relative', - left: screenWidth >= 768 ? '3%' : '0', - }} - /> - </div> - <div style={{ width: screenWidth > 768 ? '50%' : '100%' }}> - <h3 className="feat-head-desc"> - <Translate id="benefits.component.security.title">Security</Translate> - </h3> - <h2 className="feat-head add-left-margin"> - <Translate id="benefits.component.security.subtitle"> - Shield against the malicious - </Translate> - </h2> - <p className="feat-desc add-left-margin"> - <strong> - <Translate id="benefits.component.security.message"> - Apache APISIX Gateway provides multiple security plugins for identity authentication - and API verification, including CORS, JWT, Key Auth, OpenID Connect (OIDC), - Keycloak, etc. We put stability and security first. For more information, check - </Translate> - {' '} - <Link - style={{ color: '#e8433e' }} - to={useBaseUrl('docs/apisix/plugins/cors/')} - target="_blank" - rel="noopener noreferrer" - > - <Translate id="benefits.component.security.link.here">here</Translate> - </Link> - <Translate id="common.punctuation.anEnd">.</Translate> - </strong> - </p> - </div> - </div> - - <div ref={scale} className="row-benefit row-hidden"> - <div style={{ width: screenWidth > 768 ? '50%' : '100%' }}> - <h3 className="feat-head-desc"> - <Translate id="benefits.component.scalability.title"> - Scalability and availability - </Translate> - </h3> - <h2 className="feat-head add-left-margin"> - <Translate id="benefits.component.scalability.subtitle"> - Scales with your users - </Translate> - </h2> - <p className="feat-desc add-left-margin"> - <strong> - <Translate id="benefits.component.scalability.message"> - Apache APISIX Gateway provides the ability to write your own custom plugins, use - custom Load Balancing Algorithms during the balancer phase for scaling and custom - Routing algorithms for fine control on routing. - </Translate> - </strong> - </p> - </div> - <div className="benefit-infograph"> - <Scale style={{ width: screenWidth >= 768 ? '50%' : '60%' }} /> - </div> - </div> - - <div ref={dynamic} className="row-benefit row-reverse row-hidden"> - <div className="benefit-infograph"> - <Dynamic style={{ width: screenWidth >= 768 ? '50%' : '70%' }} /> - </div> - <div style={{ width: screenWidth > 768 ? '50%' : '100%' }}> - <h3 className="feat-head-desc"> - <Translate id="benefits.component.fullyDynamic.title">Fully dynamic</Translate> - </h3> - <h2 className="feat-head add-left-margin"> - <Translate id="benefits.component.fullyDynamic.subtitle"> - Save dev-time, design what matters - </Translate> - </h2> - <p className="feat-desc add-left-margin"> - <strong> - <Translate id="benefits.component.fullyDynamic.message"> - As API Gateway, Apache APISIX provides Hot updates and Hot plugins, which - continuously update configurations without restarts, saving development time and - stress. In addition, health checks, circuit breakers, and many more features keep - the system balanced. - </Translate> - </strong> - </p> - </div> - </div> - - <div ref={multiplatform} className="row-benefit row-hidden"> - <div style={{ width: screenWidth > 768 ? '50%' : '100%' }}> - <h3 className="feat-head-desc"> - <Translate id="benefits.component.multiPlatform.title"> - Multi-platform and protocol - </Translate> - </h3> - <h2 className="feat-head add-left-margin"> - <Translate id="benefits.component.multiPlatform.subtitle"> - Create once, run anywhere - </Translate> - </h2> - <p className="feat-desc add-left-margin"> - <strong> - <Translate id="benefits.component.multiPlatform.message"> - Platform agnostic, no vendor lock-in. Apache APISIX as API Management solution, can - run from bare-metal to Kubernetes. It supports HTTP to gRPC transcoding, websockets, - gRPC, Dubbo, MQTT proxy and multiple platforms including ARM64, don't worry - about the lock-in of the infra technology. - </Translate> - </strong> - </p> - </div> - <div className="benefit-infograph"> - <Multiplatform style={{ width: screenWidth >= 768 ? '50%' : '80%' }} /> - </div> - </div> - </div> - ); -}; - -export default Benefits; diff --git a/website/src/components/sections/Comparison.tsx b/website/src/components/sections/Comparison.tsx deleted file mode 100644 index c01aedd7ac3..00000000000 --- a/website/src/components/sections/Comparison.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import type { FC } from 'react'; -import React from 'react'; -import Translate from '@docusaurus/Translate'; - -import Cross from '../../assets/icons/cross.svg'; -import Tick from '../../assets/icons/tick.svg'; - -import '../../css/landing-sections/comparison.scss'; - -interface Row { - feature: JSX.Element; - apisix: JSX.Element | string; - other: JSX.Element | string; -} - -const ROWS: Row[] = [ - { - feature: <Translate id="comparison.row.throughput">Single-core throughput</Translate>, - apisix: '18,000 QPS', - other: '~1,700 QPS', - }, - { - feature: <Translate id="comparison.row.latency">Added latency</Translate>, - apisix: '0.2 ms', - other: '~2 ms', - }, - { - feature: <Translate id="comparison.row.ai">AI gateway / LLM proxy</Translate>, - apisix: <Tick title="Supported" />, - other: <Cross title="Not supported" />, - }, - { - feature: <Translate id="comparison.row.oss">Fully open-source, no paywalled features</Translate>, - apisix: <Tick title="Supported" />, - other: <Cross title="Not supported" />, - }, - { - feature: <Translate id="comparison.row.dynamic">Dynamic, real-time configuration</Translate>, - apisix: <Tick title="Supported" />, - other: <Cross title="Not supported" />, - }, - { - feature: <Translate id="comparison.row.hotreload">Hot plugin reload, no restart</Translate>, - apisix: <Tick title="Supported" />, - other: <Cross title="Not supported" />, - }, -]; - -const Comparison: FC = () => ( - <section className="compare"> - <h2 className="compare-head"> - <Translate id="comparison.component.title">Among the best, and always improving</Translate> - </h2> - <p className="compare-subtitle"> - <Translate id="comparison.component.subtitle"> - Apache APISIX is an open-source API gateway and AI gateway — here is how it compares on - performance and core capabilities. - </Translate> - </p> - <div className="compare-table-wrap"> - <table className="compare-table"> - <thead> - <tr> - <th className="compare-col-feature"> - <Translate id="comparison.col.feature">Feature</Translate> - </th> - <th className="compare-col-apisix">Apache APISIX</th> - <th className="compare-col-other"> - <Translate id="comparison.col.other">Other gateways</Translate> - </th> - </tr> - </thead> - <tbody> - {ROWS.map((row, index) => ( - <tr key={index}> - <th scope="row" className="compare-col-feature"> - {row.feature} - </th> - <td className="compare-apisix-cell">{row.apisix}</td> - <td className="compare-other-cell">{row.other}</td> - </tr> - ))} - </tbody> - </table> - </div> - </section> -); - -export default Comparison; diff --git a/website/src/components/sections/Endcta.tsx b/website/src/components/sections/Endcta.tsx deleted file mode 100644 index 8a2221fe4ca..00000000000 --- a/website/src/components/sections/Endcta.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { FC } from 'react'; -import React from 'react'; -import Link from '@docusaurus/Link'; -import Translate from '@docusaurus/Translate'; -import { LazyLoadImage } from 'react-lazy-load-image-component'; -import useWindowType from '@theme/hooks/useWindowSize'; -import useBaseUrl from '@docusaurus/useBaseUrl'; -import ArrowAnim from '../ArrowAnim'; -import style from '../../css/landing-sections/endcta.module.scss'; - -const EndCTA: FC = () => { - const windowType = useWindowType(); - - return ( - <div className={style.endcta}> - <p className={style.text}> - <span>Try </span> - <span className={style.apisix}>Apache APISIX </span> - <span>today.</span> - {windowType === 'desktop' && ( - <LazyLoadImage - className={style.rocket} - src="https://static.apiseven.com/202202/rocket.gif" - alt="Rocket" - width="6vw" - height="6vw" - /> - )} - </p> - <div className={style.links}> - <Link target="_blank" rel="noopener noreferrer" to={useBaseUrl('docs/apisix/getting-started')} className="btn btn-download"> - <Translate id="hero.component.download.btn">Getting Started</Translate> - </Link> - <ArrowAnim /> - </div> - </div> - ); -}; - -export default EndCTA; diff --git a/website/src/components/sections/Features.tsx b/website/src/components/sections/Features.tsx deleted file mode 100644 index eac61ef5389..00000000000 --- a/website/src/components/sections/Features.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import type { FC } from 'react'; -import React from 'react'; -import Translate from '@docusaurus/Translate'; - -import styles from '../../css/landing-sections/features.module.scss'; - -const Features: FC = () => ( - <div className={styles.features}> - <section className={styles.head}> - <h2> - <Translate id="features.component.why.title">Why APISIX API Gateway?</Translate> - </h2> - <h3> - <Translate id="features.component.why.subtitle"> - Reduce time fighting bugs, focus on designing world-class systems with API Gateway - </Translate> - </h3> - <p> - <Translate id="features.component.why.message"> - APISIX API Gateway offers nearly 100 open-source plugins, comprehensive API management - capabilities, and advanced technical advantages. - </Translate> - </p> - </section> - </div> -); - -export default Features; diff --git a/website/src/components/sections/HeroSection.tsx b/website/src/components/sections/HeroSection.tsx deleted file mode 100644 index 3d0e3df83c0..00000000000 --- a/website/src/components/sections/HeroSection.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import type { FC } from 'react'; -import React from 'react'; -import Link from '@docusaurus/Link'; -import Translate from '@docusaurus/Translate'; -import useBaseUrl from '@docusaurus/useBaseUrl'; -import '../../css/landing-sections/hero.scss'; - -const STATS: { value: string; label: JSX.Element }[] = [ - { value: '100+', label: <Translate id="hero.stats.plugins">plugins</Translate> }, - { value: '~18k', label: <Translate id="hero.stats.qps">QPS / core</Translate> }, - { value: '0.2 ms', label: <Translate id="hero.stats.latency">added latency</Translate> }, - { value: 'Apache 2.0', label: <Translate id="hero.stats.license">licensed</Translate> }, -]; - -const HeroSection: FC = () => ( - <header className="hero-sec-wrap"> - <div className="hero-text"> - <span className="hero-eyebrow"> - <Translate id="hero.component.eyebrow">Apache Software Foundation top-level project</Translate> - </span> - <h1 className="hero-title"> - <Translate id="hero.component.title.main">The open-source API Gateway & AI Gateway</Translate> - </h1> - <p className="hero-subtitle"> - <Translate id="hero.component.subtitle.main"> - High-performance traffic management for APIs, microservices, and LLM workloads — - dynamic routing, load balancing, authentication, observability, and 100+ plugins. - </Translate> - </p> - <div className="hero-ctas"> - <Link target="_blank" rel="noopener noreferrer" to={useBaseUrl('docs/apisix/getting-started')} className="btn btn-download"> - <Translate id="hero.component.download.btn">Get started</Translate> - </Link> - <Link target="_blank" rel="noopener noreferrer" to={useBaseUrl('plugins/')} className="btn btn-secondary"> - <Translate id="hero.component.plugins.btn">Browse 100+ plugins</Translate> - </Link> - </div> - <ul className="hero-stats"> - {STATS.map((stat) => ( - <li className="hero-stats__item" key={stat.value}> - <span className="hero-stats__value">{stat.value}</span> - <span className="hero-stats__label">{stat.label}</span> - </li> - ))} - </ul> - </div> - </header> -); - -export default HeroSection; diff --git a/website/src/components/sections/HomeEventsSection.tsx b/website/src/components/sections/HomeEventsSection.tsx deleted file mode 100644 index 88b975414b5..00000000000 --- a/website/src/components/sections/HomeEventsSection.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import type { FC } from 'react'; -import React from 'react'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; - -import Translate from '@docusaurus/Translate'; - -import style from '../../css/landing-sections/home-events.module.scss'; - -interface EventInfo { - title: string; - fileName: string; -} - -const Events: FC = () => { - const { siteConfig, i18n: { currentLocale } } = useDocusaurusContext(); - - const currentEvent = (siteConfig.customFields.events[currentLocale] - || siteConfig.customFields.events.en) as EventInfo[]; - const events = currentEvent.slice(0, 4).map((event) => { - const publishTime = event.fileName.slice(0, 10); - const fileNameArr = event.fileName.split('-'); - const url = `/blog/${fileNameArr.slice(0, 3).join('/')}/${fileNameArr.slice(3).join('-')}`; - return ( - <a className={style.eventCard} key={event.title} href={url} target="_blank" rel="noreferrer"> - <time dateTime={publishTime}>{publishTime}</time> - <h4>{event.title}</h4> - <div> - <Translate id="homeEventsSection.component.readMore">Read More</Translate> - <div className={style.arrow}> - <svg width="50%" height="100%" viewBox="0 0 256 256"> - <polygon - fill="currentcolor" - points="208,128 80,0 52.8,27.2 140.3,128 53,228.9 80,256" - /> - <rect fill="currentcolor" x="-256" y="115" width="256" height="30" /> - </svg> - </div> - </div> - </a> - ); - }); - - return <div className={style.events}>{events}</div>; -}; - -const HomeEventsSection: FC = () => ( - <div className={style.homeEvents}> - <h3> - <Translate id="homeEventsSection.component.title">Stay updated about APISIX</Translate> - </h3> - <p> - <Translate id="homeEventsSection.component.subtitle">Some Recent events</Translate> - </p> - <Events /> - <div className={style.newsletter}> - <p> - <Translate id="homeEventsSection.component.message.news"> - Stay up to date about all Apache APISIX® News, - </Translate> - {' '} - <a href="/docs/general/join/"> - <Translate id="homeEventsSection.component.link.newsletter"> - subscribe to our newsletter. - </Translate> - </a> - </p> - <a className={style.newsButton} href="/docs/general/join"> - <Translate id="homeEventsSection.component.link.Subscribe">Subscribe Now</Translate> - </a> - </div> - </div> -); - -export default HomeEventsSection; diff --git a/website/src/components/sections/Integrations.tsx b/website/src/components/sections/Integrations.tsx deleted file mode 100644 index fc9ebcb9068..00000000000 --- a/website/src/components/sections/Integrations.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import type { FC } from 'react'; -import React from 'react'; -import Head from '@docusaurus/Head'; -import Link from '@docusaurus/Link'; -import Translate from '@docusaurus/Translate'; - -import '../../css/landing-sections/integrations.scss'; - -interface Integration { - name: string; - /** Plugin name whose brand-colored icon lives in the shared plugin sprite. */ - icon?: string; - /** Colored logo served from /img/integrations/ (for non-plugin ecosystem logos). */ - logo?: string; - /** Doc link. Falls back to the plugin doc derived from `icon`. */ - href?: string; -} - -const GROUPS: { title: string; items: Integration[] }[] = [ - { - title: 'Observability & tracing', - items: [ - { name: 'Prometheus', icon: 'prometheus' }, - { name: 'Grafana', logo: '/img/integrations/grafana.svg', href: '/docs/apisix/plugins/prometheus/' }, - { name: 'Datadog', icon: 'datadog' }, - { name: 'OpenTelemetry', logo: '/img/integrations/opentelemetry.svg', href: '/docs/apisix/plugins/opentelemetry/' }, - { name: 'Apache SkyWalking', icon: 'skywalking' }, - { name: 'Zipkin', icon: 'zipkin' }, - ], - }, - { - title: 'Logging & data', - items: [ - { name: 'Elasticsearch', logo: '/img/integrations/elasticsearch.svg', href: '/docs/apisix/plugins/elasticsearch-logger/' }, - { name: 'ClickHouse', logo: '/img/integrations/clickhouse.svg', href: '/docs/apisix/plugins/clickhouse-logger/' }, - { name: 'Google Cloud', icon: 'google-cloud-logging' }, - ], - }, - { - title: 'Streaming & protocols', - items: [ - { name: 'Apache Kafka', icon: 'kafka-logger' }, - { name: 'gRPC', icon: 'grpc-transcode' }, - ], - }, - { - title: 'Service discovery & runtime', - items: [ - { name: 'Kubernetes', logo: '/img/integrations/kubernetes.svg', href: '/docs/apisix/discovery/kubernetes/' }, - { name: 'Consul', logo: '/img/integrations/consul.svg', href: '/docs/apisix/discovery/consul/' }, - { name: 'NGINX', logo: '/img/integrations/nginx.svg', href: '/docs/apisix/architecture-design/apisix/' }, - { name: 'Redis', logo: '/img/integrations/redis.svg', href: '/docs/apisix/plugins/limit-count/' }, - ], - }, - { - title: 'Security & secrets', - items: [ - { name: 'OpenID Connect', icon: 'openid-connect' }, - { name: 'Keycloak', icon: 'authz-keycloak' }, - { name: 'Casbin', icon: 'authz-casbin' }, - { name: 'HashiCorp Vault', logo: '/img/integrations/vault.svg', href: '/docs/apisix/terminology/secret/' }, - ], - }, - { - title: 'AI & LLM providers', - items: [ - { name: 'OpenAI', href: '/docs/apisix/plugins/ai-proxy/' }, - { name: 'Anthropic', href: '/docs/apisix/plugins/ai-proxy/' }, - { name: 'AWS Bedrock', href: '/docs/apisix/plugins/ai-proxy/' }, - { name: 'DeepSeek', href: '/docs/apisix/plugins/ai-proxy/' }, - { name: 'Ollama', href: '/docs/apisix/plugins/ai-proxy/' }, - ], - }, -]; - -const IntegrationTile: FC<{ item: Integration }> = ({ item }) => { - const href = item.href ?? (item.icon ? `/docs/apisix/plugins/${item.icon}/` : undefined); - const inner = ( - <> - {item.logo ? ( - <img className="integrations__img" src={item.logo} alt={item.name} loading="lazy" width={30} height={30} /> - ) : item.icon ? ( - <svg className="integrations__icon" aria-hidden="true"> - <use xlinkHref={`#icon${item.icon}`} /> - </svg> - ) : null} - <span className="integrations__name">{item.name}</span> - </> - ); - return href ? ( - <Link className="integrations__tile" to={href} title={item.name} target="_blank" rel="noopener noreferrer"> - {inner} - </Link> - ) : ( - <div className="integrations__tile" title={item.name}> - {inner} - </div> - ); -}; - -const Integrations: FC = () => ( - <section className="integrations" aria-labelledby="integrations-heading"> - <Head> - {/* Injects the shared plugin icon sprite used by #icon<name> references. */} - <script src="/js/plugin-icon.js" defer /> - </Head> - <h2 id="integrations-heading" className="integrations__heading"> - <Translate id="home.integrations.title">Integrates with your stack</Translate> - </h2> - <p className="integrations__subtitle"> - <Translate id="home.integrations.subtitle"> - Connect APISIX to your observability, security, service discovery, and AI tools through 100+ plugins. - </Translate> - </p> - <div className="integrations__tiles"> - {GROUPS.flatMap((group) => group.items).map((item) => ( - <IntegrationTile item={item} key={item.name} /> - ))} - </div> - <Link className="integrations__cta" to="/plugins/" target="_blank" rel="noopener noreferrer"> - <Translate id="home.integrations.cta">Explore all 100+ plugins</Translate> - {' →'} - </Link> - </section> -); - -export default Integrations; diff --git a/website/src/components/sections/OpensourcePromo.tsx b/website/src/components/sections/OpensourcePromo.tsx deleted file mode 100644 index 5ebca9d5b69..00000000000 --- a/website/src/components/sections/OpensourcePromo.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import type { FC } from 'react'; -import React from 'react'; -import Link from '@docusaurus/Link'; -import useBaseUrl from '@docusaurus/useBaseUrl'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import Translate from '@docusaurus/Translate'; - -import BrowserOnly from '@docusaurus/BrowserOnly'; -import type { VideoProps } from '../Video'; -import style from '../../css/landing-sections/os-promo.module.scss'; - -const VideoChannel: FC = () => { - const { - i18n: { currentLocale }, - } = useDocusaurusContext(); - - if (currentLocale.startsWith('zh')) { - return <a href="https://space.bilibili.com/551921247" target="_blank" rel="noopener noreferrer">哔哩哔哩官方账号</a>; - } - - return ( - <a href="https://www.youtube.com/channel/UCgPD18cMhOg5rmPVnQhAC8g" target="_blank" rel="noopener noreferrer"> - <Translate id="openSourcePromo.component.link.Youtube">YouTube channel</Translate> - </a> - ); -}; - -const videoOptions: VideoProps['options'] = { - controls: true, - fluid: true, - preload: 'none', - poster: 'https://static.apiseven.com/202202/apisix-video-poster.jpeg', - sources: [ - { - src: 'https://static.apiseven.com/apisix-website/videos/apisix-video/apisix-bobur.m3u8', - type: 'application/x-mpegURL', - }, - ], - width: 640, - height: 360, -}; - -const LazyLoadVideo = () => ( - <BrowserOnly> - {() => { - // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require - const Video = require('../Video').default; - return <Video options={videoOptions} />; - }} - </BrowserOnly> -); - -const OpensourcePromo: FC = () => ( - <div className={style.osPromotion}> - <div className={style.docsPromo}> - <div className={style.docsPromoHead}> - <h3> - <Translate id="openSourcePromo.component.title">New to Apache APISIX?</Translate> - </h3> - <div className={style.docsPromoSubtitle}> - <p> - <Translate id="openSourcePromo.component.subtitle.fragment1"> - New to microservices or API gateways? Get up and running with our - </Translate> - {' '} - <Link style={{ color: '#e8433e' }} to={useBaseUrl('docs')} target="_blank" rel="noopener noreferrer"> - <Translate id="openSourcePromo.component.link.docs">Docs.</Translate> - </Link> - </p> - <p> - <Translate id="openSourcePromo.component.subtitle.fragment2"> - Prefer to learn by watching? Head to our - </Translate> - {' '} - <VideoChannel /> - {' '} - <Translate id="openSourcePromo.component.subtitle.fragment3"> - for step-by-step video tutorials. - </Translate> - </p> - </div> - </div> - <div className={style.docsPromoVideo}> - <LazyLoadVideo /> - </div> - </div> - </div> -); - -export default OpensourcePromo; diff --git a/website/src/components/sections/Pathways.tsx b/website/src/components/sections/Pathways.tsx deleted file mode 100644 index 270292fc6e2..00000000000 --- a/website/src/components/sections/Pathways.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import type { FC } from 'react'; -import React from 'react'; -import Link from '@docusaurus/Link'; -import Translate from '@docusaurus/Translate'; - -import '../../css/landing-sections/pathways.scss'; - -interface Pathway { - id: string; - title: JSX.Element; - description: JSX.Element; - cta: JSX.Element; - to: string; -} - -const PATHWAYS: Pathway[] = [ - { - id: 'build', - to: '/docs/apisix/getting-started/', - title: <Translate id="home.pathways.build.title">Get started</Translate>, - description: ( - <Translate id="home.pathways.build.desc"> - Install APISIX and configure your first route in minutes. - </Translate> - ), - cta: <Translate id="home.pathways.build.cta">Read the docs</Translate>, - }, - { - id: 'learn', - to: '/learning-center/', - title: <Translate id="home.pathways.learn.title">Learn the concepts</Translate>, - description: ( - <Translate id="home.pathways.learn.desc"> - API gateway, AI gateway, rate limiting, mTLS, Kubernetes and more. - </Translate> - ), - cta: <Translate id="home.pathways.learn.cta">Explore the learning center</Translate>, - }, - { - id: 'case-studies', - to: '/blog/tags/case-studies/', - title: <Translate id="home.pathways.cases.title">Case studies</Translate>, - description: ( - <Translate id="home.pathways.cases.desc"> - See how teams run Apache APISIX in production at scale. - </Translate> - ), - cta: <Translate id="home.pathways.cases.cta">Read case studies</Translate>, - }, -]; - -const Pathways: FC = () => ( - <section className="pathways" aria-label="Get started with Apache APISIX"> - {PATHWAYS.map((pathway) => ( - <Link className="pathways__card" to={pathway.to} key={pathway.id} target="_blank" rel="noopener noreferrer"> - <h3 className="pathways__title">{pathway.title}</h3> - <p className="pathways__desc">{pathway.description}</p> - <span className="pathways__cta"> - {pathway.cta} - {' →'} - </span> - </Link> - ))} - </section> -); - -export default Pathways; diff --git a/website/src/css/landing-sections/ai-gateway-home.scss b/website/src/css/landing-sections/ai-gateway-home.scss deleted file mode 100644 index 8bb5ccd459b..00000000000 --- a/website/src/css/landing-sections/ai-gateway-home.scss +++ /dev/null @@ -1,157 +0,0 @@ -.ai-home { - background: #faf7f7; - padding: 4rem var(--ifm-spacing-horizontal); -} - -.ai-home__inner { - max-width: 1000px; - margin: 0 auto; - text-align: center; -} - -.ai-home__eyebrow { - display: inline-block; - font-size: 0.82rem; - font-weight: 600; - letter-spacing: 0.05em; - text-transform: uppercase; - color: #a32d2d; - background: #fcebeb; - padding: 5px 14px; - border-radius: 20px; - margin-bottom: 1rem; -} - -.ai-home__title { - font-size: clamp(1.6rem, 1rem + 2vw, 2.25rem); - font-weight: 700; - margin-bottom: 0.5rem; -} - -.ai-home__subtitle { - color: #6b6b6b; - max-width: 680px; - margin: 0 auto 2.25rem; - font-size: 1rem; - line-height: 1.6; -} - -.ai-home__flow { - display: flex; - align-items: center; - justify-content: center; - gap: 0.75rem; - flex-wrap: wrap; - margin-bottom: 2.5rem; -} - -.ai-home__node { - background: #fff; - border: 1px solid #e6e6e6; - border-radius: 8px; - padding: 8px 14px; - font-size: 0.9rem; - font-weight: 500; - color: #2c2c2a; -} - -.ai-home__node--apisix { - background: #e8433e; - border-color: #e8433e; - color: #fff; - font-weight: 700; -} - -.ai-home__arrow { - color: #e8433e; - font-weight: 700; -} - -.ai-home__providers { - display: flex; - gap: 6px; - flex-wrap: wrap; - justify-content: center; -} - -.ai-home__provider { - background: #fff; - border: 1px solid #e6e6e6; - border-radius: 20px; - padding: 6px 12px; - font-size: 0.82rem; - color: #444; -} - -.ai-home__provider--more { - color: #888; -} - -.ai-home__grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 1rem; - text-align: left; -} - -@media (max-width: 820px) { - .ai-home__grid { - grid-template-columns: 1fr; - } -} - -.ai-home__card { - display: block; - background: #fff; - border: 1px solid #ececec; - border-radius: 12px; - padding: 1.25rem 1.3rem; - color: inherit; - text-decoration: none; - transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease; - - &:hover { - transform: translateY(-3px); - border-color: #f3b6b3; - box-shadow: 0 6px 18px rgb(232 67 62 / 8%); - color: inherit; - text-decoration: none; - } - - &:hover .ai-home__card-title { - color: #e8433e; - } -} - -@media (prefers-reduced-motion: reduce) { - .ai-home__card:hover { - transform: none; - } -} - -.ai-home__card-title { - font-size: 1.05rem; - font-weight: 700; - margin: 0 0 0.4rem; - color: #1c1c1c; -} - -.ai-home__card-desc { - font-size: 0.9rem; - line-height: 1.55; - color: #6b6b6b; - margin: 0; -} - -.ai-home__cta { - display: inline-block; - margin-top: 2rem; - font-size: 1rem; - font-weight: 600; - color: #e8433e; - - &:hover { - color: #c7352f; - text-decoration: none; - } -} diff --git a/website/src/css/landing-sections/architecture.scss b/website/src/css/landing-sections/architecture.scss deleted file mode 100644 index 51bc3642e4e..00000000000 --- a/website/src/css/landing-sections/architecture.scss +++ /dev/null @@ -1,114 +0,0 @@ -@import '../util'; - -.hldesign { - height: 100%; - display: flex; - justify-content: center; -} - -.hldesign-graphic { - width: 60%; - height: auto; -} - -.Architecture_svg__architecture { - font-family: MaisonNeue-Medium, sans-serif; -} - -.arch { - background: linear-gradient(180deg, #e2ecff 15%, #fff 90%); - position: relative; - z-index: 1; - padding: 50px 0; - overflow: hidden; -} - -.arch-card-caption { - height: 100%; - display: flex; - justify-content: center; - color: #615d5d; -} - -.arch-card-caption > p { - width: 90%; - letter-spacing: 0.2px; - font-family: MaisonNeue-Light, sans-serif; - font-weight: 700; - text-align: center; - position: relative; - bottom: -15px; - font-size: 1rem; -} - -.arch-card-border { - z-index: -10; - position: absolute; - width: 66%; - height: 100%; - border-style: solid; - top: 15px; - left: 17%; - border-color: white; - background: white; - filter: drop-shadow(0 4px 10px rgb(0 0 0 / 25%)); -} - -.arch-head, -.testimonials-head, -.docs-promo-head { - font-size: 2.4rem; - position: relative; - left: 16.8%; - width: 83.2%; -} - -.arch-subtitle, -.testimonials-subtitle, -.docs-promo-subtitle { - font-size: 1.13rem; - position: relative; - left: 16.8%; - width: 70%; - color: #615d5d; - letter-spacing: 0.2px; - font-family: MaisonNeue-Light, sans-serif; - font-weight: 700; -} - -.add-left-margin { - position: relative; - left: 16.8%; -} - -@include respond-between(sm, md) { - .hldesign-graphic { - width: 100%; - } - - .arch-scale-svg { - display: none; - } - - .arch-card-border { - width: 100%; - left: 0; - top: 10px; - } - - .arch-head, - .testimonials-head, - .docs-promo-head, - .oss-promo-head { - left: 7%; - width: 83%; - } - - .arch-subtitle, - .testimonials-subtitle, - .docs-promo-subtitle, - .oss-promo-subtitle { - left: 7%; - width: 85%; - } -} diff --git a/website/src/css/landing-sections/benefits.scss b/website/src/css/landing-sections/benefits.scss deleted file mode 100644 index 8e51831be71..00000000000 --- a/website/src/css/landing-sections/benefits.scss +++ /dev/null @@ -1,39 +0,0 @@ -.row-benefit { - margin: 100px 5px; - display: flex; - justify-content: center; - align-items: center; - height: fit-content; -} - -.benefit { - background: #fff8f6; - padding: 50px 0; -} - -.benefit-infograph { - width: 50%; - display: flex; - justify-content: center; - align-items: center; -} - -.feat-head-desc { - font-size: 1.4rem; - color: #363130; - opacity: 0.5; - position: relative; - left: 17%; - margin: 0; - line-height: 1.8; - width: fit-content; -} - -.feat-desc { - margin: 10px 0; - font-size: 1.13rem; - color: #615d5d; - font-family: MaisonNeue-Light, sans-serif; - width: 65%; - left: 17%; -} diff --git a/website/src/css/landing-sections/comparison.scss b/website/src/css/landing-sections/comparison.scss deleted file mode 100644 index 9131ed9ffb8..00000000000 --- a/website/src/css/landing-sections/comparison.scss +++ /dev/null @@ -1,95 +0,0 @@ -.compare { - max-width: 900px; - margin: 0 auto; - padding: 3.5rem var(--ifm-spacing-horizontal); - text-align: center; -} - -.compare-head { - font-size: clamp(1.6rem, 1rem + 2vw, 2.25rem); - font-weight: 700; - max-width: 760px; - margin: 0 auto 0.5rem; -} - -.compare-subtitle { - color: #6b6b6b; - max-width: 640px; - margin: 0 auto 2.25rem; - font-size: 1rem; - line-height: 1.6; -} - -.compare-table-wrap { - overflow-x: auto; -} - -.compare-table { - width: fit-content; - max-width: 100%; - margin: 0 auto; - border-collapse: separate; - border-spacing: 0; - background: #fff; - border: 1px solid #ececec; - border-radius: 12px; - overflow: hidden; - font-size: 1rem; -} - -.compare-table th, -.compare-table td { - padding: 0.95rem 1.1rem; - text-align: center; - border-bottom: 1px solid #f0f0f0; -} - -.compare-table tbody tr:last-child th, -.compare-table tbody tr:last-child td { - border-bottom: none; -} - -.compare-table thead th { - font-weight: 700; - font-size: 1.02rem; - background: #fafafa; - border-bottom: 1px solid #ececec; -} - -.compare-table tbody th[scope="row"] { - font-weight: 500; - color: #2c2c2a; -} - -.compare-col-feature { - text-align: left; -} - -.compare-col-apisix, -.compare-apisix-cell { - background: #fff5f4; -} - -.compare-col-apisix { - color: #e8433e; -} - -.compare-col-apisix, -.compare-col-other { - min-width: 160px; -} - -.compare-apisix-cell { - font-weight: 700; - color: #1c1c1c; -} - -.compare-other-cell { - color: #8a8a8a; -} - -.compare-table svg { - width: 22px; - height: 22px; - vertical-align: middle; -} diff --git a/website/src/css/landing-sections/endcta.module.scss b/website/src/css/landing-sections/endcta.module.scss deleted file mode 100644 index e8a0f320456..00000000000 --- a/website/src/css/landing-sections/endcta.module.scss +++ /dev/null @@ -1,71 +0,0 @@ -@import "../util"; - -$apisix-color: #e8433e; - -.endcta { - padding: max(14vh, 100px) 2rem; - margin: 4em 10% 0; - background-color: #f8f8f8; - display: flex; - flex-direction: column; - align-items: center; - overflow: hidden; - border-radius: 1em; - - & .text { - display: flex; - justify-content: center; - align-items: center; - white-space: pre; - text-transform: capitalize; - font-size: 4.5rem; - font-weight: bolder; - font-family: MaisonNeue-ExtraBold, sans-serif; - position: relative; - width: fit-content; - - & .apisix { - color: $apisix-color; - } - - & .rocket { - $size: 10rem; - - position: absolute; - right: -#{$size}; - width: $size; - height: $size; - bottom: 0.3em; - transform: rotate(45deg); - } - } - - & .links { - display: flex; - } - - @include respond-below(lg) { - & .text { - & .rocket { - display: none; - } - } - } - - @include respond-below(sm) { - min-height: 50vh; - margin: 4rem 1rem 0; - align-items: flex-start; - padding: 100px 2rem; - - & .text { - flex-direction: column; - font-size: 2.5rem; - align-items: flex-start; - - & .apisix { - margin: -1rem 0; - } - } - } -} diff --git a/website/src/css/landing-sections/features.module.scss b/website/src/css/landing-sections/features.module.scss deleted file mode 100644 index 9694c585167..00000000000 --- a/website/src/css/landing-sections/features.module.scss +++ /dev/null @@ -1,322 +0,0 @@ -@import "../util"; - -.dashboardLink { - color: hsl(0deg 0% 80%); - width: fit-content; - - &:hover { - color: hsl(0deg 0% 80%); - } -} - -.dashboard { - background-color: hsl(0deg 100% 0%); - font-family: MaisonNeue-Bold, sans-serif; - margin-left: -9px; - - --height: 130px; - --width: calc(var(--height) * 2.048); - --radius: calc(var(--height) * 0.168); - - height: var(--height); - width: var(--width); - border-radius: var(--radius); - padding: calc(var(--radius) - 6px); - display: flex; - flex-direction: column; - position: relative; - overflow: hidden; - transform: scale3d(0.95, 0.95, 1); - transition: all 200ms cubic-bezier(0.65, 0.05, 0.36, 1); - - &::before { - content: ""; - width: calc(100% - 6px); - height: calc(100% - 6px); - border-radius: inherit; - position: absolute; - top: 3px; - left: 3px; - backdrop-filter: blur(2px); - opacity: 0; - transition: opacity 300ms ease-in-out; - } - - &::after { - content: "APISIX"; - position: absolute; - top: 5px; - left: -35%; - font-size: 8.5rem; - font-weight: bolder; - line-height: 1; - color: #fff; - opacity: 0.1; - } - - & .dashboardTitle { - font-size: 1.35rem; - font-family: MaisonNeue-ExtraBold, sans-serif; - white-space: nowrap; - flex-grow: 1; - background-image: - linear-gradient( - 135deg, - hsl(0deg 73% 50%), - hsl(227deg 90% 65%) - ); - background-clip: text; - -webkit-text-fill-color: transparent; - width: fit-content; - position: relative; - } - - & .dashboardAccount { - font-variant: small-caps; - line-height: normal; - letter-spacing: 1px; - position: relative; - opacity: 0.8; - - &::after { - content: "↑"; - font-family: MaisonNeue-Light, sans-serif; - font-size: 2.5rem; - line-height: 2rem; - position: absolute; - top: 2px; - right: 8px; - opacity: 0; - color: hsl(0deg 0% 70%); - transform: rotate(90deg); - transition: opacity 300ms ease-in-out; - } - } - - &:hover { - transform: scale3d(1, 1, 1); - box-shadow: - 0 0 0 1px hsl(213deg 26% 28% / 2%), - 0 3px 2px hsl(0deg 0% 0% / 14%), - 0 7px 5px hsl(0deg 0% 0% / 12%), - 0 13px 10px hsl(0deg 0% 0% / 5%), - 0 22px 17px hsl(0deg 0% 0% / 8%); - - & .dashboardTitle { - animation: pulse 3s linear infinite; - } - - & .dashboardAccount { - opacity: 0.9; - - &::after { - opacity: 0.6; - animation: bounce 1.8s ease-in-out infinite; - } - } - } - - &:active { - transform: scale3d(0.95, 0.95, 1); - } -} - -@keyframes pulse { - from { - filter: hue-rotate(0); - } - - to { - filter: hue-rotate(360deg); - } -} - -@keyframes bounce { - from, - 20%, - 53%, - to { - animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); - transform: rotate(90deg) translate3d(0, 0, 0); - } - - 40%, - 43% { - animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - transform: rotate(90deg) translate3d(0, 30px, 0) scaleY(1.1); - } - - 70% { - animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - transform: rotate(90deg) translate3d(0, 15px, 0) scaleY(1.05); - } - - 80% { - transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); - transform: rotate(90deg) translate3d(0, 0, 0) scaleY(0.95); - } - - 90% { - transform: rotate(90deg) translate3d(0, 4px, 0) scaleY(1.02); - } -} - -.features { - width: 100%; - margin: 6rem 0 2rem; - hyphens: auto; - position: relative; - - & .head { - padding: 0 10%; - margin-bottom: 0; - - h2 { - background-color: rgb(232 67 62); - display: block; - width: fit-content; - color: white; - padding: 6px 20px; - border-radius: 50rem; - } - - h3 { - font-size: 3.25rem; - } - - p { - font-size: 1.5rem; - color: hsl(0deg 0% 78%); - } - } - - & .container { - display: flex; - flex-direction: column; - - & .feat { - display: flex; - height: 100vh; - - & .left, - & .right { - display: flex; - width: 50%; - } - - & .left { - padding-right: 5%; - justify-content: flex-end; - align-items: center; - - & > section { - display: flex; - flex-direction: column; - width: 500px; - - h2 { - font-size: 3.25rem; - } - - & > div, - & > p { - font-size: 1.25rem; - margin-bottom: 4em; - - & .action { - display: flex; - margin-top: 1em; - } - } - } - } - - & .right { - flex-shrink: 0; - - img { - object-position: left top; - object-fit: cover; - height: 80vh; - align-self: center; - border: 1px solid hsl(0deg 0% 0% / 12%); - border-radius: 1rem 0 0 1rem; - } - } - } - } - - @include respond-below(md) { - margin: 4rem 1rem; - align-items: flex-start; - - & .container { - & .feat { - flex-direction: column; - height: auto; - - & .left, - & .right { - width: 100%; - justify-content: flex-start; - padding: 0 10%; - } - - & .left { - & > section { - width: auto; - - div { - margin-bottom: 0; - } - } - } - - & .right { - img { - height: 50vh; - border-radius: 1rem; - margin: 1rem 0 4rem; - } - } - } - } - } - - @include respond-below(sm) { - width: auto; - - & .head { - padding: 0; - - h2 { - font-size: 1.25rem; - } - - h3 { - font-size: 2.25rem; - } - - p { - font-size: 1rem; - } - } - - & .container { - & .feat { - & .left, - & .right { - padding: 0; - } - - & .left { - & > section { - h2 { - font-size: 2.25rem; - } - } - } - } - } - } -} diff --git a/website/src/css/landing-sections/hero.scss b/website/src/css/landing-sections/hero.scss deleted file mode 100644 index 29fecfbbe81..00000000000 --- a/website/src/css/landing-sections/hero.scss +++ /dev/null @@ -1,249 +0,0 @@ -@import "../util"; - -@keyframes fade-in-up { - from { - opacity: 0; - transform: translate3d(0, 50%, 0); - } - - to { - opacity: 1; - transform: translate3d(0, 0, 0); - } -} - -.hero-title, -.hero-subtitle, -.hero-ctas { - animation-name: fade-in-up; - animation-fill-mode: both; - animation-delay: 0s; - animation-duration: 1s; -} - -.hero-subtitle { - z-index: 100; - font-size: 1.15rem; - font-family: MaisonNeue-Light, sans-serif; - position: relative; - color: #5f5d5d; - line-height: 1.6; - letter-spacing: 0.2px; - max-width: 620px; - margin: 1.25rem 0 0; - animation-delay: 0.2s; -} - -.hero-ctas { - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; - gap: 12px; - margin-top: 1.75rem; - animation-delay: 0.4s; -} - -.hero-sec-wrap { - display: flex; - justify-content: center; - background: #f7f7f8; - padding: clamp(3rem, 9vh, 7rem) 24px clamp(2.5rem, 7vh, 5rem); -} - -.hero-text { - width: 100%; - max-width: 780px; - display: flex; - flex-direction: column; - align-items: center; - text-align: center; -} - -.hero-title { - font-family: MaisonNeue-Bold, sans-serif; - color: #121212; - max-width: 780px; - font-size: clamp(2.4rem, 1rem + 4.2vw, 4rem); - line-height: 1.08; - letter-spacing: 0.2px; - margin: 0; -} - -.btn { - background: #030c16; - border-style: solid; - border-width: 1px; - border: none; - color: white; - padding: 5px 20px; - border-radius: 20px; - font-size: 1rem; - text-decoration: none; - margin: 0 10px 0 0; - transition: all 0.3s; -} - -.btn:hover { - background: #9b9b9b; - color: white; - text-decoration: none; -} - -.btn-docs { - display: flex; - flex-direction: row; - align-items: center; -} - -.btn-docs:hover { - text-decoration: none; -} - -.btn-docs .goto { - color: #000; -} - -.btn-docs:hover .goto, -.btn-docs:active .goto { - opacity: 0.8; -} - -.btn-docs .arrow { - width: 30px; - height: 30px; - position: relative; -} - -.btn-docs .arrow svg { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -.btn-docs .arrow polygon, -.btn-docs .arrow rect { - transition: all 250ms; -} - -.btn-docs .arrow rect { - opacity: 0; -} - -.btn-docs:hover .arrow polygon { - transform: translateX(100px); -} - -.btn-docs:hover .arrow rect { - transform: translateX(240px); - opacity: 1; -} - -@include respond-between(md, lg) { - .hero-title { - font-size: clamp(3rem, 1rem + 5vw, 4.5rem); - } -} - -@media (max-width: 996px) { - .hero-title, - .hero-subtitle, - .hero-ctas { - animation: none; - } - - .hero-sec-wrap { - min-height: calc(100svh - var(--ifm-navbar-height)); - height: auto; - padding: 72px 0 48px; - } - - .hero-text { - width: 100%; - padding: 0 24px; - align-content: center; - } - - .hero-title { - width: 100%; - max-width: 680px; - font-size: clamp(2.4rem, 12vw, 4.2rem); - line-height: 1.05; - } - - .hero-subtitle { - width: 100%; - max-width: 680px; - padding-right: 0; - margin: 18px 0; - } - - .hero-ctas { - flex-wrap: wrap; - gap: 12px; - } -} - -@media (prefers-reduced-motion: reduce) { - .hero-title, - .hero-subtitle, - .hero-ctas { - animation: none; - } -} - -.hero-eyebrow { - display: inline-block; - font-size: 0.82rem; - font-weight: 600; - color: #a32d2d; - background: #fcebeb; - padding: 5px 14px; - border-radius: 20px; - margin-bottom: 1.25rem; -} - -.btn-secondary { - background: #fff; - color: #1c1c1c; - border: 1px solid #d6d6d6; -} - -.btn-secondary:hover { - background: #f1f1f1; - color: #1c1c1c; - border-color: #c0c0c0; -} - -.hero-stats { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 1.5rem 2.5rem; - list-style: none; - width: 100%; - max-width: 640px; - margin: 2.25rem 0 0; - padding: 1.5rem 0 0; - border-top: 1px solid #e6e6e6; -} - -.hero-stats__item { - display: flex; - flex-direction: column; - align-items: center; -} - -.hero-stats__value { - font-size: 1.5rem; - font-weight: 700; - color: #e8433e; - line-height: 1.1; -} - -.hero-stats__label { - font-size: 0.85rem; - color: #777; - margin-top: 2px; -} diff --git a/website/src/css/landing-sections/home-events.module.scss b/website/src/css/landing-sections/home-events.module.scss deleted file mode 100644 index f4cc5bb66ce..00000000000 --- a/website/src/css/landing-sections/home-events.module.scss +++ /dev/null @@ -1,146 +0,0 @@ -@import '../util'; - -$transition-duration: 200ms; - -.arrow { - $size: 25px; - - width: $size; - height: $size; - position: relative; - margin-bottom: 0.5px; - - svg { - position: absolute; - top: 50%; - left: 50%; - transform: translate3d(-50%, -50%, 0); - overflow: visible; - } - - polygon, - rect { - transition: all $transition-duration ease-in-out; - } - - rect { - opacity: 0; - } -} - -.homeEvents { - display: flex; - flex-direction: column; - align-items: center; - margin: 8em 10%; - - h3 { - font-size: 2.4rem; - } - - p { - color: #615d5d; - font-size: 1.13rem; - letter-spacing: 0.2px; - font-family: MaisonNeue-Light, sans-serif; - font-weight: 700; - } - - & .events { - display: flex; - justify-content: center; - - & .eventCard { - display: flex; - flex-direction: column; - font-size: 0.9rem; - height: 200px; - width: 282px; - transition: all $transition-duration ease-in-out; - margin: 0 1rem; - border-radius: 1rem; - border: 1px solid hsl(210deg 26% 28% / 14%); - padding: 1rem; - - & > time { - color: hsl(0deg 0% 78%); - } - - & > h4 { - flex-shrink: 0; - flex-grow: 1; - color: hsl(0deg 0% 30%); - } - - & > div { - color: hsl(0deg 0% 60%); - display: flex; - align-items: center; - } - - &:hover { - transform: scale3d(1.03, 1.03, 1); - - polygon { - transform: translateX(100px) !important; - } - - rect { - transform: translateX(240px) !important; - opacity: 1 !important; - } - } - } - } - - .newsletter { - display: flex; - font-family: MaisonNeue-Light, sans-serif; - font-size: 0.95rem; - color: #615d5d; - letter-spacing: 0.2px; - align-items: center; - justify-content: center; - flex-direction: column; - margin: 25px 0; - - p { - margin: 2rem 0 1rem; - } - } - - .newsButton { - border: none; - padding: 5px 30px; - border-style: solid; - border-radius: 20px; - background: #030c16; - color: white; - margin: 0; - text-decoration: none; - transition: all 0.3s; - - &:hover { - background: #9b9b9b; - color: white; - text-decoration: none; - } - } - - @include respond-below(sm) { - margin: 4rem 1rem; - align-items: flex-start; - - & .events { - overflow-x: scroll; - width: 100%; - display: flex; - justify-content: flex-start; - padding-bottom: 1rem; - - & .eventCard { - flex-shrink: 0; - } - } - } -} diff --git a/website/src/css/landing-sections/integrations.scss b/website/src/css/landing-sections/integrations.scss deleted file mode 100644 index 1d1cfb64469..00000000000 --- a/website/src/css/landing-sections/integrations.scss +++ /dev/null @@ -1,89 +0,0 @@ -.integrations { - max-width: 960px; - margin: 0 auto; - padding: 3.5rem var(--ifm-spacing-horizontal) 1rem; - text-align: center; -} - -.integrations__heading { - font-size: clamp(1.6rem, 1rem + 2vw, 2.25rem); - font-weight: 700; - margin-bottom: 0.5rem; -} - -.integrations__subtitle { - color: #6b6b6b; - max-width: 620px; - margin: 0 auto 2.25rem; - font-size: 1rem; - line-height: 1.6; -} - -.integrations__tiles { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 0.75rem; - max-width: 860px; - margin: 0 auto; -} - -.integrations__tile { - display: flex; - align-items: center; - gap: 0.6rem; - padding: 0.65rem 1.1rem; - border: 1px solid #ececec; - border-radius: 0.75rem; - background: #fff; - color: inherit; - text-decoration: none; - transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease; - - &:hover { - transform: translateY(-3px); - border-color: #f3b6b3; - box-shadow: 0 6px 18px rgb(232 67 62 / 8%); - color: inherit; - text-decoration: none; - } -} - -.integrations__icon { - width: 30px; - height: 30px; - flex-shrink: 0; -} - -.integrations__img { - width: 30px; - height: 30px; - object-fit: contain; - flex-shrink: 0; -} - -.integrations__name { - font-size: 0.95rem; - font-weight: 500; - color: #2c2c2a; - white-space: nowrap; -} - -.integrations__cta { - display: inline-block; - margin-top: 2rem; - font-size: 1rem; - font-weight: 600; - color: #e8433e; - - &:hover { - color: #c7352f; - text-decoration: none; - } -} - -@media (prefers-reduced-motion: reduce) { - .integrations__tile:hover { - transform: none; - } -} diff --git a/website/src/css/landing-sections/os-promo.module.scss b/website/src/css/landing-sections/os-promo.module.scss deleted file mode 100644 index 5ea407059b7..00000000000 --- a/website/src/css/landing-sections/os-promo.module.scss +++ /dev/null @@ -1,53 +0,0 @@ -@import "../util"; - -.osPromotion { - margin: 4rem 10%; - - a { - color: #e8433e; - } - - & .docsPromo { - box-sizing: border-box; - display: flex; - flex-direction: column; - align-items: center; - background-color: #f8f8f8; - padding: 60px 40px; - border-radius: 1rem; - - & .docsPromoHead { - display: flex; - flex-flow: column; - max-width: 760px; - margin-bottom: 2rem; - text-align: center; - - h3 { - font-size: 2.4rem; - } - } - - & .docsPromoSubtitle { - font-size: 1.13rem; - position: relative; - color: #615d5d; - letter-spacing: 0.2px; - font-family: MaisonNeue-Light, sans-serif; - font-weight: 700; - } - - & .docsPromoVideo { - width: 100%; - max-width: 720px; - } - } - - @include respond-below(sm) { - margin: 3rem 1rem; - - & .docsPromo { - padding: 40px 20px; - } - } -} diff --git a/website/src/css/landing-sections/pathways.scss b/website/src/css/landing-sections/pathways.scss deleted file mode 100644 index 01312e5d666..00000000000 --- a/website/src/css/landing-sections/pathways.scss +++ /dev/null @@ -1,62 +0,0 @@ -.pathways { - max-width: 1000px; - margin: 0 auto; - padding: 1rem var(--ifm-spacing-horizontal) 1.5rem; - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 1.25rem; -} - -@media (max-width: 820px) { - .pathways { - grid-template-columns: 1fr; - } -} - -.pathways__card { - display: flex; - flex-direction: column; - background: #fff; - border: 1px solid #ececec; - border-top: 3px solid #e8433e; - border-radius: 0.75rem; - padding: 1.6rem 1.4rem; - color: inherit; - transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease; - - &:hover { - transform: translateY(-4px); - box-shadow: 0 10px 28px rgba(0, 0, 0, 0.07); - border-color: #f3b6b3; - border-top-color: #e8433e; - color: inherit; - text-decoration: none; - } -} - -.pathways__title { - font-size: 1.25rem; - font-weight: 700; - margin: 0 0 0.5rem; - color: #1c1c1c; -} - -.pathways__desc { - font-size: 0.95rem; - line-height: 1.55; - color: #6b6b6b; - margin: 0 0 1rem; - flex: 1; -} - -.pathways__cta { - font-size: 0.95rem; - font-weight: 600; - color: #e8433e; -} - -@media (prefers-reduced-motion: reduce) { - .pathways__card:hover { - transform: none; - } -} diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx deleted file mode 100644 index 65a89d98656..00000000000 --- a/website/src/pages/index.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import type { FC } from 'react'; -import React, { useEffect } from 'react'; -import useThemeContext from '@theme/hooks/useThemeContext'; -import Layout from '@theme/Layout'; -import Head from '@docusaurus/Head'; -import { translate } from '@docusaurus/Translate'; -import useWindowType from '@theme/hooks/useWindowSize'; - -import HeroSection from '../components/sections/HeroSection'; -import Architecture from '../components/sections/Architecture'; -import Features from '../components/sections/Features'; -import Benefits from '../components/sections/Benefits'; -import Comparison from '../components/sections/Comparison'; -import OpensourcePromo from '../components/sections/OpensourcePromo'; -import EndCTA from '../components/sections/Endcta'; -import Pathways from '../components/sections/Pathways'; -import Integrations from '../components/sections/Integrations'; -import AIGatewaySection from '../components/sections/AIGatewaySection'; - -// 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(); - - useEffect(() => { - if (windowType === 'mobile') { - // remove mode switch at navbar-sidebar - const sidebarModeSwitch = document.querySelector('div.navbar-sidebar__brand > div') as HTMLDivElement; - if (sidebarModeSwitch) { - sidebarModeSwitch.style.display = 'none'; - } - } else { - // remove mode switch at navbar - const navbarModeSwitch = document.querySelector('div.navbar__items.navbar__items--right > div.react-toggle') as HTMLDivElement; - if (navbarModeSwitch) { - navbarModeSwitch.style.display = 'none'; - } - } - }, [windowType]); - - useEffect(() => { - if (isDarkTheme) { - setLightTheme(); - } - }, [isDarkTheme]); - - return (null); -}; - -const Index: FC = () => ( - <Layout> - <ThemeResetComponent /> - <Head> - <title>{translate({ id: 'homepage.meta.title', message: 'Apache APISIX - Open Source API Gateway & AI Gateway' })}</title> - <link rel="canonical" href="https://apisix.apache.org/" /> - <meta - name="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." - /> - <meta - property="og:title" - content="Apache APISIX - Open Source API Gateway & AI Gateway" - /> - <meta - property="og: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." - /> - <meta property="og:url" content="https://apisix.apache.org/" /> - <meta - name="twitter:title" - content="Apache APISIX - Open Source API Gateway & AI Gateway" - /> - <meta - 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 /> - <Pathways /> - <AIGatewaySection /> - <Architecture /> - <Features /> - <Integrations /> - <Benefits /> - <Comparison /> - <OpensourcePromo /> - <EndCTA /> - </Layout> -); - -export default Index;
