This is an automated email from the ASF dual-hosted git repository. moonming pushed a commit to branch feat/seo-homepage-rewrite in repository https://gitbox.apache.org/repos/asf/apisix-website.git
commit 3a7f7dd6a3a74ff756b38a21223ad4552f3b69ce Author: Ming Wen <[email protected]> AuthorDate: Mon Jun 22 14:23:24 2026 +0800 feat(seo): rewrite homepage for CTR + funnel to docs/pillars/plugins - Add 'Trusted by teams everywhere' auto-scrolling logo marquee (pure-CSS transform, hover-pause, reduced-motion safe; logos pluggable via CDN). - Add 'Integrates with your stack' grid reusing the real plugin-sprite icons (Prometheus, Datadog, SkyWalking, Zipkin, Kafka, gRPC, OIDC, Keycloak...). - Add intent 'Pathways' cards routing to docs, learning center, and plugin hub. - Slim the hero: drop the WebGL HeroCanvas (load-speed win), add eyebrow, second CTA, and a trust-stats row; center the text layout. - Compose: hero -> trusted-by -> pathways -> product -> integrations -> ... --- website/src/components/sections/HeroSection.tsx | 70 ++++++------- website/src/components/sections/Integrations.tsx | 95 +++++++++++++++++ website/src/components/sections/Pathways.tsx | 67 ++++++++++++ website/src/components/sections/TrustedBy.tsx | 80 +++++++++++++++ website/src/css/landing-sections/hero.scss | 114 ++++++++++++++------- website/src/css/landing-sections/integrations.scss | 97 ++++++++++++++++++ website/src/css/landing-sections/pathways.scss | 62 +++++++++++ website/src/css/landing-sections/trusted-by.scss | 89 ++++++++++++++++ website/src/pages/index.tsx | 6 ++ 9 files changed, 603 insertions(+), 77 deletions(-) diff --git a/website/src/components/sections/HeroSection.tsx b/website/src/components/sections/HeroSection.tsx index f30f108b146..3dab2641758 100644 --- a/website/src/components/sections/HeroSection.tsx +++ b/website/src/components/sections/HeroSection.tsx @@ -2,61 +2,49 @@ import type { FC } from 'react'; import React from 'react'; import Link from '@docusaurus/Link'; import Translate from '@docusaurus/Translate'; - -import BrowserOnly from '@docusaurus/BrowserOnly'; -import useWindowType from '@theme/hooks/useWindowSize'; import useBaseUrl from '@docusaurus/useBaseUrl'; -import ArrowAnim from '../ArrowAnim'; import '../../css/landing-sections/hero.scss'; -const LazyLoadHeroCanvas = () => { - const windowType = useWindowType(); - if (windowType === 'mobile') return null; - - return ( - <BrowserOnly> - {() => { - // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require - const HeroCanvas = require('../HeroCanvas').default; - return <HeroCanvas />; - }} - </BrowserOnly> - ); -}; +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 = () => ( - <div className="hero-sec-wrap" style={{ width: '100%' }}> + <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.fragment1">API Gateway & AI Gateway for APIs and AI Agents</Translate> + <Translate id="hero.component.title.main">The open-source API Gateway & AI Gateway</Translate> </h1> - <h2 - className="hero-subtitle" - style={{ - color: '#E8433E', fontSize: 32, fontWeight: 700, lineHeight: 1.2, - }} - > - <Translate id="hero.component.title.fragment2"> - Open-Source, Community-Driven, Future-Ready - </Translate> - </h2> - <h3 className="hero-subtitle"> - <Translate id="hero.component.subtitle.content"> - APISIX API Gateway provides rich traffic management features like load balancing, dynamic - upstream, canary release, circuit breaking, auth, and observability. + <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> - </h3> + </p> <div className="hero-ctas"> <Link target="_parent" to={useBaseUrl('docs/apisix/getting-started')} className="btn btn-download"> - <Translate id="hero.component.download.btn">Getting Started</Translate> + <Translate id="hero.component.download.btn">Get started</Translate> + </Link> + <Link target="_parent" to={useBaseUrl('plugins/')} className="btn btn-secondary"> + <Translate id="hero.component.plugins.btn">Browse 100+ plugins</Translate> </Link> - <ArrowAnim /> </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> - <div className="add-margin"> - <LazyLoadHeroCanvas /> - </div> - </div> + </header> ); export default HeroSection; diff --git a/website/src/components/sections/Integrations.tsx b/website/src/components/sections/Integrations.tsx new file mode 100644 index 00000000000..65254a2c673 --- /dev/null +++ b/website/src/components/sections/Integrations.tsx @@ -0,0 +1,95 @@ +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; + /** + * Optional logo URL, for ecosystem logos that are not plugin icons (e.g. + * Kubernetes, OpenTelemetry, LLM providers). Host official colored SVGs on + * the API7 CDN and set this; it takes precedence over `icon`. + */ + logo?: string; +} + +const GROUPS: { title: string; items: Integration[] }[] = [ + { + title: 'Observability & tracing', + items: [ + { name: 'Prometheus', icon: 'prometheus' }, + { name: 'Datadog', icon: 'datadog' }, + { name: 'Apache SkyWalking', icon: 'skywalking' }, + { name: 'Zipkin', icon: 'zipkin' }, + { name: 'Google Cloud', icon: 'google-cloud-logging' }, + ], + }, + { + title: 'Authentication & security', + items: [ + { name: 'OpenID Connect', icon: 'openid-connect' }, + { name: 'Keycloak', icon: 'authz-keycloak' }, + { name: 'Casbin', icon: 'authz-casbin' }, + ], + }, + { + title: 'Streaming & protocols', + items: [ + { name: 'Apache Kafka', icon: 'kafka-logger' }, + { name: 'gRPC', icon: 'grpc-transcode' }, + ], + }, +]; + +const IntegrationTile: FC<{ item: Integration }> = ({ item }) => ( + <div className="integrations__tile" title={item.name}> + {item.logo ? ( + <img className="integrations__img" src={item.logo} alt={item.name} loading="lazy" width={40} height={40} /> + ) : ( + <svg className="integrations__icon" aria-hidden="true"> + <use xlinkHref={`#icon${item.icon}`} /> + </svg> + )} + <span className="integrations__name">{item.name}</span> + </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, and streaming tools through 100+ plugins. + </Translate> + </p> + <div className="integrations__groups"> + {GROUPS.map((group) => ( + <div className="integrations__group" key={group.title}> + <div className="integrations__group-title">{group.title}</div> + <div className="integrations__tiles"> + {group.items.map((item) => ( + <IntegrationTile item={item} key={item.name} /> + ))} + </div> + </div> + ))} + </div> + <Link className="integrations__cta" to="/plugins/"> + <Translate id="home.integrations.cta">Explore all 100+ plugins</Translate> + {' →'} + </Link> + </section> +); + +export default Integrations; diff --git a/website/src/components/sections/Pathways.tsx b/website/src/components/sections/Pathways.tsx new file mode 100644 index 00000000000..76105343df6 --- /dev/null +++ b/website/src/components/sections/Pathways.tsx @@ -0,0 +1,67 @@ +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: 'plugins', + to: '/plugins/', + title: <Translate id="home.pathways.plugins.title">Browse plugins</Translate>, + description: ( + <Translate id="home.pathways.plugins.desc"> + 100+ plugins for auth, security, traffic control, observability and AI. + </Translate> + ), + cta: <Translate id="home.pathways.plugins.cta">Open the plugin hub</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}> + <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/components/sections/TrustedBy.tsx b/website/src/components/sections/TrustedBy.tsx new file mode 100644 index 00000000000..59f0cefb7d4 --- /dev/null +++ b/website/src/components/sections/TrustedBy.tsx @@ -0,0 +1,80 @@ +import type { FC } from 'react'; +import React, { useState } from 'react'; +import Link from '@docusaurus/Link'; +import Translate, { translate } from '@docusaurus/Translate'; + +import '../../css/landing-sections/trusted-by.scss'; + +interface TrustedUser { + name: string; + link: string; + /** + * Optional logo URL. When omitted, a clean text wordmark is shown instead, so + * the section renders correctly before official logos are available. + * Host official customer logos on the API7 CDN (where the showcase logos + * already live) and set this to e.g. + * `https://static.apiseven.com/uploads/users/nasa.svg`. + */ + logo?: string; +} + +const USERS: TrustedUser[] = [ + { name: 'NASA', link: 'https://www.jpl.nasa.gov/' }, + { name: 'Zoom', link: 'https://zoom.us/' }, + { name: 'HPE', link: 'https://www.hpe.com/' }, + { name: 'Tencent', link: 'https://www.tencent.com/en-us/' }, + { name: 'Hisense', link: 'https://global.hisense.com/' }, + { name: 'k6', link: 'https://k6.io/' }, + { name: 'Citi', link: 'https://www.citigroup.com/' }, + { name: 'API7.ai', link: 'https://api7.ai/' }, + { name: "McDonald's", link: 'https://www.mcdonalds.com/' }, + { name: 'KFC', link: 'https://global.kfc.com/' }, +]; + +const UserLogo: FC<{ user: TrustedUser }> = ({ user }) => { + const [failed, setFailed] = useState(!user.logo); + return ( + <Link + className="trusted-by__item" + to={user.link} + target="_blank" + rel="noopener noreferrer" + aria-label={user.name} + > + {failed ? ( + <span className="trusted-by__wordmark">{user.name}</span> + ) : ( + <img + className="trusted-by__logo" + src={user.logo} + alt={user.name} + loading="lazy" + width={132} + height={36} + onError={() => setFailed(true)} + /> + )} + </Link> + ); +}; + +const TrustedBy: FC = () => ( + <section + className="trusted-by" + aria-label={translate({ id: 'home.trustedBy.label', message: 'Organizations using Apache APISIX' })} + > + <div className="trusted-by__eyebrow"> + <Translate id="home.trustedBy.title">Trusted by teams everywhere</Translate> + </div> + <div className="trusted-by__viewport"> + {/* Duplicated once so the CSS marquee loops seamlessly at translateX(-50%). */} + <div className="trusted-by__track"> + {[...USERS, ...USERS].map((user, index) => ( + <UserLogo key={`${user.name}-${index}`} user={user} /> + ))} + </div> + </div> + </section> +); + +export default TrustedBy; diff --git a/website/src/css/landing-sections/hero.scss b/website/src/css/landing-sections/hero.scss index 61bdaa5005f..29fecfbbe81 100644 --- a/website/src/css/landing-sections/hero.scss +++ b/website/src/css/landing-sections/hero.scss @@ -23,57 +23,49 @@ .hero-subtitle { z-index: 100; - font-size: 1.1rem; + font-size: 1.15rem; font-family: MaisonNeue-Light, sans-serif; position: relative; - color: #615d5d; - line-height: 30px; + color: #5f5d5d; + line-height: 1.6; letter-spacing: 0.2px; - margin: 25px 0; - padding-right: 20px; - animation-delay: 0.25s; + max-width: 620px; + margin: 1.25rem 0 0; + animation-delay: 0.2s; } .hero-ctas { display: flex; align-items: center; - animation-delay: 0.5s; + justify-content: center; + flex-wrap: wrap; + gap: 12px; + margin-top: 1.75rem; + animation-delay: 0.4s; } .hero-sec-wrap { display: flex; - background: #f4f4f4ad; - height: 100vh; -} - -.homeCanvas-overlay { - position: absolute; - width: 50vw; - height: 100vh; - background: #fff; - top: -1px; - right: 0; -} - -.homeCanvas { - width: 50vw; - height: 100vh; + justify-content: center; + background: #f7f7f8; + padding: clamp(3rem, 9vh, 7rem) 24px clamp(2.5rem, 7vh, 5rem); } .hero-text { - height: 100%; + width: 100%; + max-width: 780px; display: flex; - flex-wrap: wrap; - align-content: center; - width: 50%; - padding: 0 0 0 6vw; + flex-direction: column; + align-items: center; + text-align: center; } .hero-title { font-family: MaisonNeue-Bold, sans-serif; color: #121212; - width: 42vw; - font-size: 4.2rem; + max-width: 780px; + font-size: clamp(2.4rem, 1rem + 4.2vw, 4rem); + line-height: 1.08; letter-spacing: 0.2px; margin: 0; } @@ -150,12 +142,7 @@ @include respond-between(md, lg) { .hero-title { - width: 40vw; - font-size: 5.2rem; - } - - .hero-subtitle { - width: 40vw; + font-size: clamp(3rem, 1rem + 5vw, 4.5rem); } } @@ -205,3 +192,58 @@ 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/integrations.scss b/website/src/css/landing-sections/integrations.scss new file mode 100644 index 00000000000..4fb36e7b630 --- /dev/null +++ b/website/src/css/landing-sections/integrations.scss @@ -0,0 +1,97 @@ +.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__groups { + display: flex; + flex-direction: column; + gap: 1.75rem; +} + +.integrations__group-title { + font-size: 0.78rem; + letter-spacing: 0.07em; + text-transform: uppercase; + color: #9a9a9a; + margin-bottom: 0.85rem; +} + +.integrations__tiles { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.75rem; +} + +.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; + 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 rgba(232, 67, 62, 0.08); + } +} + +.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/pathways.scss b/website/src/css/landing-sections/pathways.scss new file mode 100644 index 00000000000..01312e5d666 --- /dev/null +++ b/website/src/css/landing-sections/pathways.scss @@ -0,0 +1,62 @@ +.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/css/landing-sections/trusted-by.scss b/website/src/css/landing-sections/trusted-by.scss new file mode 100644 index 00000000000..fc2a5c28004 --- /dev/null +++ b/website/src/css/landing-sections/trusted-by.scss @@ -0,0 +1,89 @@ +.trusted-by { + padding: 3rem 0 2.5rem; + text-align: center; +} + +.trusted-by__eyebrow { + font-size: 0.8rem; + letter-spacing: 0.09em; + text-transform: uppercase; + color: #8a8a8a; + margin-bottom: 1.5rem; +} + +.trusted-by__viewport { + overflow: hidden; + // Fade the logos in/out at both edges instead of cutting them off hard. + -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%); + mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%); +} + +.trusted-by__track { + display: flex; + width: max-content; + align-items: center; + gap: 3.5rem; + // Compositor-only animation (transform). The track holds two identical + // copies of the list, so translateX(-50%) lands exactly on the loop seam. + animation: trusted-by-marquee 45s linear infinite; + will-change: transform; +} + +.trusted-by:hover .trusted-by__track { + animation-play-state: paused; +} + +.trusted-by__item { + display: flex; + align-items: center; + justify-content: center; + + &:hover { + text-decoration: none; + } +} + +.trusted-by__logo { + height: 34px; + width: auto; + object-fit: contain; + filter: grayscale(1); + opacity: 0.65; + transition: filter 0.2s ease, opacity 0.2s ease; + + .trusted-by__item:hover & { + filter: grayscale(0); + opacity: 1; + } +} + +.trusted-by__wordmark { + font-size: 1.15rem; + font-weight: 700; + white-space: nowrap; + color: #7a7975; + transition: color 0.2s ease; + + .trusted-by__item:hover & { + color: #e8433e; + } +} + +@keyframes trusted-by-marquee { + from { + transform: translateX(0); + } + to { + transform: translateX(-50%); + } +} + +@media (prefers-reduced-motion: reduce) { + .trusted-by__track { + animation: none; + width: auto; + flex-wrap: wrap; + justify-content: center; + gap: 2rem 3rem; + } +} diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx index a57c738c3c3..672772387c0 100644 --- a/website/src/pages/index.tsx +++ b/website/src/pages/index.tsx @@ -13,6 +13,9 @@ 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 TrustedBy from '../components/sections/TrustedBy'; +import Pathways from '../components/sections/Pathways'; +import Integrations from '../components/sections/Integrations'; // Structured data for the homepage. Organization + WebSite are already injected // globally by config/schema-org.js; these add product-level (SoftwareApplication) @@ -136,8 +139,11 @@ const Index: FC = () => ( <script type="application/ld+json">{JSON.stringify(HOMEPAGE_FAQ_SCHEMA)}</script> </Head> <HeroSection /> + <TrustedBy /> + <Pathways /> <Architecture /> <Features /> + <Integrations /> <Benefits /> <Comparison /> <OpensourcePromo />
