This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris-website.git
The following commit(s) were added to refs/heads/master by this push:
new b5fd2f94f46 [refactor](next) modify key features (#3624)
b5fd2f94f46 is described below
commit b5fd2f94f46b55b7518a5607fabb1f124f1d28e6
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Thu May 7 13:48:34 2026 -0700
[refactor](next) modify key features (#3624)
---
.github/workflows/build-check.yml | 1 +
.github/workflows/cron-deploy-website.yml | 1 +
.github/workflows/docs-next-build.yml | 1 +
.github/workflows/manual-deploy-website.yml | 1 +
.gitignore | 1 +
.../key-features/compute-storage-decoupled.mdx | 11 +
docs-next/key-features/high-availability.mdx | 21 +
.../tuning/tuning-plan/dml-tuning-plan.md | 1 +
.../tuning/tuning-plan/dml-tuning-plan.md | 1 +
package.json | 5 +
scripts/key-features/generate.js | 142 +++++
src/components/home-next/NavbarNext.tsx | 2 +-
src/components/use-cases-next/AIAnalyticsNext.tsx | 35 +-
.../use-cases-next/CustomerFacingAnalyticsNext.tsx | 36 +-
.../use-cases-next/DataWarehousingNext.tsx | 31 +-
.../use-cases-next/ObservabilityNext.tsx | 39 +-
src/components/why-doris-next/KeyFeaturesNext.scss | 691 +++++++++++++++++++++
src/components/why-doris-next/KeyFeaturesNext.tsx | 250 ++++++++
src/pages/why-doris/key-features/index.tsx | 6 +
19 files changed, 1195 insertions(+), 81 deletions(-)
diff --git a/.github/workflows/build-check.yml
b/.github/workflows/build-check.yml
index 96314c864a5..81f1fc30d20 100644
--- a/.github/workflows/build-check.yml
+++ b/.github/workflows/build-check.yml
@@ -196,6 +196,7 @@ jobs:
yarn cache clean
export NODE_OPTIONS=--max-old-space-size=8192
yarn
+ yarn key-features:generate
echo "Building with DOCS_VERSIONS=${DOCS_VERSIONS:-all}"
PWA_SERVICE_WORKER_URL=https://doris.apache.org/sw.js yarn
docusaurus build ${{ steps.detect.outputs.locale_args }}
diff --git a/.github/workflows/cron-deploy-website.yml
b/.github/workflows/cron-deploy-website.yml
index 6fe7c3390db..486ee65fe88 100644
--- a/.github/workflows/cron-deploy-website.yml
+++ b/.github/workflows/cron-deploy-website.yml
@@ -39,6 +39,7 @@ jobs:
export NODE_OPTIONS=--max-old-space-size=8192
yarn
node ./scripts/update_github_info.js
+ yarn key-features:generate
PWA_SERVICE_WORKER_URL=https://doris.apache.org/sw.js yarn
docusaurus build --locale en --locale zh-CN
if [ ! -d "./ja-build" ]; then
echo "ja-build directory not found, aborting to avoid
publishing incorrect ja content."
diff --git a/.github/workflows/docs-next-build.yml
b/.github/workflows/docs-next-build.yml
index 87d35ec5ead..06731855b80 100644
--- a/.github/workflows/docs-next-build.yml
+++ b/.github/workflows/docs-next-build.yml
@@ -75,6 +75,7 @@ jobs:
DOCS_VERSIONS: '4.x'
run: |
export NODE_OPTIONS=--max-old-space-size=8192
+ yarn key-features:generate
yarn docusaurus build --locale en --locale zh-CN
- name: Verify docs-next artifacts
diff --git a/.github/workflows/manual-deploy-website.yml
b/.github/workflows/manual-deploy-website.yml
index b8a8b985b00..abcb2502f34 100644
--- a/.github/workflows/manual-deploy-website.yml
+++ b/.github/workflows/manual-deploy-website.yml
@@ -42,6 +42,7 @@ jobs:
yarn cache clean
export NODE_OPTIONS=--max-old-space-size=8192
yarn
+ yarn key-features:generate
PWA_SERVICE_WORKER_URL=https://doris.apache.org/sw.js yarn
docusaurus build --locale en --locale zh-CN
if [ ! -d "./ja-build" ]; then
echo "ja-build directory not found, aborting to avoid
publishing incorrect ja content."
diff --git a/.gitignore b/.gitignore
index 2b3044e8673..61baea771f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
# Generated files
.docusaurus
.cache-loader
+/src/generated/key-features.ts
# Misc
.DS_Store
diff --git a/docs-next/key-features/compute-storage-decoupled.mdx
b/docs-next/key-features/compute-storage-decoupled.mdx
new file mode 100644
index 00000000000..19cf6ebbe44
--- /dev/null
+++ b/docs-next/key-features/compute-storage-decoupled.mdx
@@ -0,0 +1,11 @@
+---
+title: Compute-Storage Decoupled
+description: Scale compute and storage independently with a decoupled Doris
architecture.
+slug: /key-features/compute-storage-decoupled
+featureCard:
+ tags:
+ - cloud
+ - elastic
+ href: /docs-next/dev/compute-storage-decoupled/intro
+---
+
diff --git a/docs-next/key-features/high-availability.mdx
b/docs-next/key-features/high-availability.mdx
new file mode 100644
index 00000000000..64f11c1f587
--- /dev/null
+++ b/docs-next/key-features/high-availability.mdx
@@ -0,0 +1,21 @@
+---
+title: High Availability
+description: Keep Doris available through replication, failover, and fault
tolerance.
+slug: /key-features/high-availability
+featureCard:
+ tags:
+ - fault-tolerance
+ - replica
+---
+
+High availability is the set of mechanisms that help Doris stay online when
nodes fail or need maintenance. Replication and failover are central to that
behavior.
+
+## When to use
+
+Use this feature whenever the cluster must tolerate node loss or maintenance
events without significant service interruption.
+
+## Key points
+
+- Replication protects data availability.
+- Operational procedures matter as much as system design.
+- Failover planning should be part of deployment planning.
diff --git a/docs-next/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md
b/docs-next/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md
index 15e9255c2c6..eda822ccae2 100644
--- a/docs-next/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md
+++ b/docs-next/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md
@@ -1,6 +1,7 @@
---
{
"title": "DML Plan Tuning: Locating Load and Query Performance
Bottlenecks",
+ "sidebar_label": "DML Plan Tuning",
"language": "en",
"description": "How to tune Doris DML plans? This article explains how to
distinguish between load and query bottlenecks, and provides entry points to
best practices for both load and query tuning.",
"keywords": ["Doris DML tuning", "load performance bottleneck", "query
performance bottleneck", "DML plan tuning", "Doris load best practices"]
diff --git
a/i18n/zh-CN/docusaurus-plugin-content-docs-next/current/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md
b/i18n/zh-CN/docusaurus-plugin-content-docs-next/current/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md
index b71caa1680c..b8138d70254 100644
---
a/i18n/zh-CN/docusaurus-plugin-content-docs-next/current/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md
+++
b/i18n/zh-CN/docusaurus-plugin-content-docs-next/current/query-acceleration/tuning/tuning-plan/dml-tuning-plan.md
@@ -1,6 +1,7 @@
---
{
"title": "DML 计划调优:定位导入与查询性能瓶颈",
+ "sidebar_label": "DML 计划调优",
"language": "zh-CN",
"description": "如何对 Doris DML 计划进行调优?本文介绍如何区分导入与查询瓶颈,并提供导入与查询调优的最佳实践入口。",
"keywords": ["Doris DML 调优", "导入性能瓶颈", "查询性能瓶颈", "DML 计划调优", "Doris
导入最佳实践"]
diff --git a/package.json b/package.json
index e133faa642c..3a7437e93d0 100644
--- a/package.json
+++ b/package.json
@@ -8,8 +8,12 @@
"start:zh-CN": "docusaurus start --locale zh-CN",
"start:ja": "docusaurus start --locale ja",
"postinstall": "node scripts/patch-search-tokenize.js",
+ "prestart": "yarn key-features:generate",
"build": "PWA_SERVICE_WORKER_URL=https://doris.apache.org/sw.js
docusaurus build",
+ "prebuild": "yarn key-features:generate",
"build:version": "docusaurus set-versions && docusaurus build",
+ "prebuild:version": "yarn key-features:generate",
+ "pretypecheck": "yarn key-features:generate",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
@@ -37,6 +41,7 @@
"docs:lint": "node scripts/docs-governance/report.js --format github",
"docs:lint:changed": "node scripts/docs-governance/report.js --changed
--format github",
"docs:report": "node scripts/docs-governance/report.js --format json
--output website-quality-governance/generated/docs-governance-report.json",
+ "key-features:generate": "node scripts/key-features/generate.js",
"docs-governance:test": "node --test
scripts/docs-governance/__tests__/*.test.js",
"typecheck": "tsc"
},
diff --git a/scripts/key-features/generate.js b/scripts/key-features/generate.js
new file mode 100644
index 00000000000..90c4df86f45
--- /dev/null
+++ b/scripts/key-features/generate.js
@@ -0,0 +1,142 @@
+#!/usr/bin/env node
+
+const fs = require('fs');
+const path = require('path');
+const matter = require('gray-matter');
+const {
+ ensureDirForFile,
+ normalizePath,
+ parseArgs,
+ stripMarkdownExtension,
+ walkMarkdownFiles,
+} = require('../docs-governance/lib');
+
+const DOCS_DIR = 'docs-next/key-features';
+const DOCS_ROUTE_BASE = '/docs-next/dev';
+const OUTPUT_FILE = 'src/generated/key-features.ts';
+
+function slugToRoute(slug, fallbackSlug) {
+ const raw = typeof slug === 'string' && slug.trim() ? slug.trim() :
fallbackSlug;
+ const normalized = raw.replace(/^\/+/, '/').replace(/\/+$/, '');
+ return normalized === '/' ? '/' : normalized;
+}
+
+function resolveHref(frontMatter, docsRelativePath) {
+ const cardHref = frontMatter?.featureCard?.href;
+ if (typeof cardHref === 'string' && cardHref.trim()) {
+ return cardHref.trim();
+ }
+
+ const fallbackSlug = `/${stripMarkdownExtension(docsRelativePath)}`;
+ const routeSlug = slugToRoute(frontMatter.slug, fallbackSlug);
+ return `${DOCS_ROUTE_BASE}${routeSlug}`;
+}
+
+function normalizeTags(value, sourcePath) {
+ if (!Array.isArray(value) || value.length === 0) {
+ throw new Error(`Missing featureCard.tags in ${sourcePath}`);
+ }
+ return value.map((tag) => {
+ if (typeof tag !== 'string' || !tag.trim()) {
+ throw new Error(`Invalid featureCard.tags entry in ${sourcePath}`);
+ }
+ return tag.trim();
+ });
+}
+
+function parseFeatureDoc(rootDir, absPath, index) {
+ const raw = fs.readFileSync(absPath, 'utf8');
+ const parsed = matter(raw);
+ const data = parsed.data || {};
+ const featureCard = data.featureCard || {};
+ const relativePath = normalizePath(path.relative(rootDir, absPath));
+ const docsRelativePath = normalizePath(path.relative(path.join(rootDir,
'docs-next'), absPath));
+ const sourcePath = relativePath;
+ const title = typeof data.title === 'string' ? data.title.trim() : '';
+ const description = typeof data.description === 'string' ?
data.description.trim() : '';
+
+ if (!title) {
+ throw new Error(`Missing required front matter field "title" in
${sourcePath}`);
+ }
+ if (!description) {
+ throw new Error(`Missing required front matter field "description" in
${sourcePath}`);
+ }
+
+ const href = resolveHref(data, docsRelativePath);
+ const badge =
+ typeof featureCard.badge === 'string' && featureCard.badge.trim()
+ ? featureCard.badge.trim()
+ : typeof featureCard.href === 'string' && featureCard.href.trim()
+ ? 'doc'
+ : undefined;
+ const id = stripMarkdownExtension(docsRelativePath).replace(/\//g, '-');
+ return {
+ id,
+ title,
+ description,
+ tags: normalizeTags(featureCard.tags, sourcePath),
+ href,
+ ...(badge ? { badge } : {}),
+ sourcePath,
+ };
+}
+
+function generate({ rootDir = process.cwd(), output = OUTPUT_FILE } = {}) {
+ const absDocsDir = path.join(rootDir, DOCS_DIR);
+ if (!fs.existsSync(absDocsDir)) {
+ throw new Error(`Missing docs directory: ${DOCS_DIR}`);
+ }
+
+ const files = walkMarkdownFiles(rootDir, [DOCS_DIR]);
+ const cards = files.map((absPath, index) => parseFeatureDoc(rootDir,
absPath, index));
+ cards.sort((a, b) => a.title.localeCompare(b.title) ||
a.href.localeCompare(b.href));
+
+ const seenIds = new Set();
+ const seenHref = new Set();
+ for (const card of cards) {
+ if (seenIds.has(card.id)) {
+ throw new Error(`Duplicate key-features id: ${card.id}`);
+ }
+ if (seenHref.has(card.href)) {
+ throw new Error(`Duplicate key-features href: ${card.href}`);
+ }
+ seenIds.add(card.id);
+ seenHref.add(card.href);
+ }
+
+ const outputPath = path.resolve(rootDir, output);
+ ensureDirForFile(outputPath);
+ const file = `/* AUTO-GENERATED FILE. DO NOT EDIT DIRECTLY. */
+export type KeyFeatureCard = {
+ id: string;
+ title: string;
+ description: string;
+ tags: string[];
+ href: string;
+ badge?: string;
+};
+
+export const keyFeatureCards: KeyFeatureCard[] = ${JSON.stringify(
+ cards.map(({ sourcePath, ...card }) => card),
+ null,
+ 2,
+ )};
+`;
+ fs.writeFileSync(outputPath, `${file}\n`, 'utf8');
+ return cards;
+}
+
+function runCli() {
+ const args = parseArgs(process.argv.slice(2));
+ const rootDir = args.root ? path.resolve(process.cwd(), args.root) :
process.cwd();
+ const output = args.output || OUTPUT_FILE;
+ generate({ rootDir, output });
+}
+
+if (require.main === module) {
+ runCli();
+}
+
+module.exports = {
+ generate,
+};
diff --git a/src/components/home-next/NavbarNext.tsx
b/src/components/home-next/NavbarNext.tsx
index 8d758908c63..881c1b81b96 100644
--- a/src/components/home-next/NavbarNext.tsx
+++ b/src/components/home-next/NavbarNext.tsx
@@ -35,9 +35,9 @@ function buildNavItems(docsHref: string, releasesHref:
string): NavItem[] {
{
label: 'Why Doris',
items: [
- { label: 'Key Features (coming soon)', href: '#' },
{ label: 'Doris vs. Others', href: '/why-doris/compare' },
{ label: 'Benchmarks (coming soon)', href: '#' },
+ { label: 'Key Features', href: '/why-doris/key-features' },
{ label: 'User Stories (coming soon)', href: '#' },
],
},
diff --git a/src/components/use-cases-next/AIAnalyticsNext.tsx
b/src/components/use-cases-next/AIAnalyticsNext.tsx
index a699f6ad0c4..f96d67bc811 100644
--- a/src/components/use-cases-next/AIAnalyticsNext.tsx
+++ b/src/components/use-cases-next/AIAnalyticsNext.tsx
@@ -486,13 +486,6 @@ const capabilities: Capability[] = [
poweredLabel: 'Powered by',
poweredBy: [
'VARIANT data type',
- 'JSON analytics',
- 'Dynamic schema support',
- 'Nested field access',
- 'Flexible ingestion',
- 'Semi-structured analytics',
- 'Auto-columnar storage for hot fields',
- 'SQL exploration over dynamic data',
],
},
{
@@ -509,15 +502,12 @@ const capabilities: Capability[] = [
'Apache Doris brings SQL filtering, full-text search, BM25
relevance scoring, and vector search into one analytical engine for RAG,
enterprise search, AI copilots, agent memory, and AI observability. Teams can
retrieve the right context from documents, logs, prompts, responses, feedback,
and embeddings while applying business filters such as tenant, user, time
range, permissions, model version, and workflow status.',
poweredLabel: 'Powered by',
poweredBy: [
- 'Inverted index',
- 'Tokenized full-text search',
- 'BM25 relevance scoring',
- 'Vector index (HNSW)',
- 'Vector similarity search',
- 'Structured SQL filters',
- 'JSON and metadata filters',
- 'Hybrid search in SQL',
- 'RAG-ready retrieval',
+ 'Inverted Index',
+ 'Full-text Search',
+ 'BM25',
+ 'Vector Index',
+ 'Embedding',
+ 'Reciprocal Rank Fusion',
],
},
{
@@ -535,15 +525,8 @@ const capabilities: Capability[] = [
poweredLabel: 'Powered by',
poweredBy: [
'LLM SQL functions',
- 'Text summarization in SQL',
- 'Sentiment analysis in SQL',
- 'Classification and extraction',
- 'MCP server',
- 'SQL interface for agents and tools',
- 'API and connector integration',
- 'Agent framework integration',
- 'LLMOps and observability integration',
- 'Unified analytics for AI signals',
+ 'MCP Server',
+ 'Semantic Layer(Coming Soon)',
],
},
];
@@ -750,7 +733,7 @@ function CtaSection(): JSX.Element {
with <span className="accent">Apache Doris.</span>
</h2>
<div className="cta-actions" data-reveal data-reveal-delay="2">
- <Link className="btn btn-yellow" to="/download">
+ <Link className="btn btn-yellow"
to="/docs-next/dev/getting-started/quick-start">
<svg
width="14"
height="14"
diff --git a/src/components/use-cases-next/CustomerFacingAnalyticsNext.tsx
b/src/components/use-cases-next/CustomerFacingAnalyticsNext.tsx
index 9ca3d60677a..7ecc7009309 100644
--- a/src/components/use-cases-next/CustomerFacingAnalyticsNext.tsx
+++ b/src/components/use-cases-next/CustomerFacingAnalyticsNext.tsx
@@ -422,9 +422,13 @@ const capabilities: Capability[] = [
desc: 'New data becomes queryable within seconds, while dashboards and
in-product analytics stay fast and interactive. Users see the latest activity
as it happens, not after the next batch cycle.',
poweredLabel: 'Powered by',
poweredBy: [
- { label: 'Stream ingestion from Kafka and CDC', href: '#' },
- { label: 'Incremental materialized view refresh', href: '#' },
- { label: 'Real-time query visibility', href: '#' },
+ { label: 'Load Transaction', href: '#' },
+ { label: 'Data Compaction', href: '#' },
+ { label: 'Data Update/Delete', href: '#' },
+ { label: 'Preaggregation', href: '#' },
+ { label: 'Group Commit', href: '#' },
+ { label: 'Kafka/CDC Integration', href: '#' },
+ { label: 'Incremental Materialized View', href: '#' },
],
},
{
@@ -440,10 +444,13 @@ const capabilities: Capability[] = [
desc: 'Apache Doris sustains fast, predictable query response times as
concurrent users and data volumes grow, not just in single-query benchmarks.',
poweredLabel: 'Powered by',
poweredBy: [
- { label: 'MPP execution engine', href: '#' },
- { label: 'Vectorized execution', href: '#' },
- { label: 'Advanced query optimizer', href: '#' },
- { label: 'Columnar storage and compression', href: '#' },
+ { label: 'Data Pruning', href: '#' },
+ { label: 'High Concurrency Point Query', href: '#' },
+ { label: 'Vectorized Execution', href: '#' },
+ { label: 'Columnar Storage', href: '#' },
+ { label: 'Prepared Statement', href: '#' },
+ { label: 'Query Cache', href: '#' },
+ { label: 'Condition Cache', href: '#' },
],
},
{
@@ -459,9 +466,10 @@ const capabilities: Capability[] = [
desc: 'Serve many users, teams, or tenants from a single platform.
Isolate workloads and control resource usage so heavy queries from one tenant
do not impact others.',
poweredLabel: 'Powered by',
poweredBy: [
- { label: 'Resource isolation', href: '#' },
- { label: 'High-concurrency scheduling', href: '#' },
- { label: 'Query queueing', href: '#' },
+ { label: 'Workload Group', href: '#' },
+ { label: 'Resource Group', href: '#' },
+ { label: 'Compute Group', href: '#' },
+ { label: 'Pipleline Execution Engine', href: '#' },
],
},
{
@@ -475,9 +483,9 @@ const capabilities: Capability[] = [
desc: 'Query open lakehouse formats directly. Combine real-time
serving with existing data lake architectures, without copying data twice.',
poweredLabel: 'Powered by',
poweredBy: [
- { label: 'Iceberg and lakehouse query capabilities', href: '#' },
- { label: 'Zero-copy analytics', href: '#' },
- { label: 'Unified access to operational and historical data',
href: '#' },
+ { label: 'Parquet Reader Optzimation', href: '#' },
+ { label: 'Data Cache & Page Cache', href: '#' },
+ { label: 'Metadata Cache', href: '#' },
],
},
];
@@ -653,7 +661,7 @@ function CTASection(): JSX.Element {
with <span className="accent">Apache Doris.</span>
</h2>
<div className="cta-actions" data-reveal data-reveal-delay="2">
- <Link className="btn btn-yellow" to="/download">
+ <Link className="btn btn-yellow"
to="/docs-next/dev/getting-started/quick-start">
<svg
width="14"
height="14"
diff --git a/src/components/use-cases-next/DataWarehousingNext.tsx
b/src/components/use-cases-next/DataWarehousingNext.tsx
index 574f7006225..2a02dd5b711 100644
--- a/src/components/use-cases-next/DataWarehousingNext.tsx
+++ b/src/components/use-cases-next/DataWarehousingNext.tsx
@@ -419,9 +419,9 @@ const capabilities: Capability[] = [
desc: 'Support detail records, aggregated metrics, real-time upserts,
and analytical datasets in one engine. Doris combines flexible table models
with partitioning and bucketing to improve data organization, query
performance, and reuse.',
poweredLabel: 'Powered by',
poweredBy: [
- { label: 'Duplicate Key Model', href: '#' },
- { label: 'Aggregate Key Model', href: '#' },
- { label: 'Unique Key Model', href: '#' },
+ { label: 'Data Model', href: '#' },
+ { label: 'Rollup', href: '#' },
+ { label: 'Analytic Functions', href: '#' },
{ label: 'Partitioning and Bucketing', href: '#' },
],
},
@@ -438,10 +438,12 @@ const capabilities: Capability[] = [
desc: 'Apache Doris continuously ingests streaming and CDC data,
applies updates in real time, and makes fresh data queryable within seconds, so
analytics can move beyond overnight batch pipelines.',
poweredLabel: 'Powered by',
poweredBy: [
- { label: 'Built-in MySQL/PostgreSQL CDC capabilities', href: '#' },
- { label: 'Built-in Kafka message subscription capabilities', href:
'#' },
- { label: 'High-concurrency, low-latency data ingestion', href: '#'
},
- { label: 'Real-time data update capabilities', href: '#' },
+ { label: 'Load Transaction', href: '#' },
+ { label: 'Data Compaction', href: '#' },
+ { label: 'Data Update/Delete', href: '#' },
+ { label: 'Preaggregation', href: '#' },
+ { label: 'Group Commit', href: '#' },
+ { label: 'Kafka/CDC Integration', href: '#' },
],
},
{
@@ -457,9 +459,10 @@ const capabilities: Capability[] = [
desc: 'Incrementally refreshed materialized views keep derived
datasets fresh without full recomputation, while Doris reliably executes large
SQL jobs for backfills, end-of-day processing, and historical workloads.',
poweredLabel: 'Powered by',
poweredBy: [
- { label: 'Incremental Materialized Views', href: '#' },
- { label: 'Binlog, Table Stream, and Dynamic Table (Coming soon)',
href: '#' },
+ { label: 'Batch Load', href: '#' },
+ { label: 'Incremental Materialized View', href: '#' },
{ label: 'Spill to Disk', href: '#' },
+ { label: 'Binlog/Table Stream (Coming Soon)', href: '#' },
],
},
{
@@ -475,8 +478,8 @@ const capabilities: Capability[] = [
desc: 'Query, write, and manage open lakehouse tables directly with
Doris. Use one SQL layer to access Iceberg, Hudi, Delta Lake, JDBC sources, and
internal Doris tables, with Iceberg lifecycle operations such as writes,
updates, and compaction.',
poweredLabel: 'Powered by',
poweredBy: [
- { label: 'Multi-Catalog: Iceberg, Hudi, Delta Lake, JDBC, and
more', href: '#' },
- { label: 'Data and metadata cache layer', href: '#' },
+ { label: 'Multi Catalog', href: '#' },
+ { label: 'Managing Lake Table', href: '#' },
],
},
{
@@ -493,11 +496,11 @@ const capabilities: Capability[] = [
poweredLabel: 'Powered by',
poweredBy: [
{
- label: 'Pluggable authentication and authorization modules:
LDAP, RBAC, OIDC, and more',
+ label: 'Pluggable authentication and authorization modules',
href: '#',
},
{ label: 'Catalog integrations: Glue, Iceberg REST, and more',
href: '#' },
- { label: 'Workload Group for resource isolation', href: '#' },
+ { label: 'Data Lineage', href: '#' },
],
},
];
@@ -671,7 +674,7 @@ function CtaSection(): JSX.Element {
with <span className="accent">Apache Doris.</span>
</h2>
<div className="cta-actions" data-reveal data-reveal-delay="2">
- <Link className="btn btn-yellow" to="/download">
+ <Link className="btn btn-yellow"
to="/docs-next/dev/getting-started/quick-start">
<svg
width="14"
height="14"
diff --git a/src/components/use-cases-next/ObservabilityNext.tsx
b/src/components/use-cases-next/ObservabilityNext.tsx
index c6dae44a522..5a7daea890b 100644
--- a/src/components/use-cases-next/ObservabilityNext.tsx
+++ b/src/components/use-cases-next/ObservabilityNext.tsx
@@ -445,11 +445,11 @@ const capabilities: Capability[] = [
'Apache Doris ingests high-volume telemetry from Kafka, CDC
pipelines, and streaming APIs with low latency. Logs, metrics, traces, and AI
agent events become queryable in near real time for fast debugging, monitoring,
and cost analysis.',
poweredLabel: 'Powered by',
poweredBy: [
- 'Routine Load',
- 'Stream Load',
- 'Built-in MySQL / PostgreSQL CDC',
- 'Built-in Kafka subscription',
- 'Real-time data update',
+ 'Stream Load via HTTP',
+ 'Kafka/CDC Integration',
+ 'Group Commit',
+ 'Prepared Statement',
+ 'Vertical',
],
},
{
@@ -467,10 +467,6 @@ const capabilities: Capability[] = [
poweredLabel: 'Powered by',
poweredBy: [
'VARIANT data type',
- 'JSON analysis',
- 'Dynamic schema support',
- 'Nested field access',
- 'Auto-columnar storage for hot fields',
],
},
{
@@ -487,11 +483,9 @@ const capabilities: Capability[] = [
'Search massive volumes of logs, prompts, responses and tool
outputs with inverted indexes, tokenized text search and BM25 relevance
scoring, so teams can quickly find failures and understand agent behavior.',
poweredLabel: 'Powered by',
poweredBy: [
- 'Inverted index',
- 'Full-text search',
- 'Tokenized text search',
- 'BM25 relevance scoring',
- 'Search over prompts and tool outputs',
+ 'Inverted Index',
+ 'Full-text Search',
+ 'BM25',
],
},
{
@@ -508,12 +502,7 @@ const capabilities: Capability[] = [
'Doris accelerates dashboard analytics over observability metrics
with a high-performance OLAP engine, materialized views, and transparent SQL
query rewriting — from error rates and latency to token usage, model cost, and
SLA trends.',
poweredLabel: 'Powered by',
poweredBy: [
- 'Columnar storage',
- 'MPP query engine',
- 'Vectorized execution',
- 'Materialized views',
- 'Rollup / pre-aggregation',
- 'High-concurrency dashboard queries',
+ 'Prepared Statement',
],
},
{
@@ -530,11 +519,9 @@ const capabilities: Capability[] = [
'Doris 4.0 unifies structured filtering, full-text search and
vector similarity search in SQL. Teams can search prompts, responses, tool
outputs and traces by metadata, keywords and semantic similarity, all in one
query.',
poweredLabel: 'Powered by',
poweredBy: [
- 'Vector index (HNSW)',
- 'Vector similarity search',
- 'Full-text search',
- 'Inverted index',
- 'Structured SQL filtering',
+ 'Vector Index',
+ 'Embedding',
+ 'Reciprocal Rank Fusion',
],
},
];
@@ -699,7 +686,7 @@ function CtaSection(): JSX.Element {
with <span className="accent">Apache Doris.</span>
</h2>
<div className="cta-actions" data-reveal data-reveal-delay="2">
- <Link className="btn btn-yellow" to="/download">
+ <Link className="btn btn-yellow"
to="/docs-next/dev/getting-started/quick-start">
<svg
width="14"
height="14"
diff --git a/src/components/why-doris-next/KeyFeaturesNext.scss
b/src/components/why-doris-next/KeyFeaturesNext.scss
new file mode 100644
index 00000000000..8cd5d005990
--- /dev/null
+++ b/src/components/why-doris-next/KeyFeaturesNext.scss
@@ -0,0 +1,691 @@
+.kf-next {
+ --kf-green-dark: #06805F;
+ --kf-green-darker: #054C39;
+ --kf-green-darkest: #033A2C;
+ --kf-green-deep: #0B7A58;
+ --kf-green-glow: #2DDFA8;
+ --kf-cream-light: #FAF6EE;
+ --kf-cream-warm: #EFE6D2;
+ --kf-cream: #F5EFE4;
+ --kf-paper: #FFFCF5;
+ --kf-yellow: #FFD23F;
+ --kf-yellow-bright: #FFE066;
+ --kf-coral: #FF5C39;
+ --kf-ink: #0F1A14;
+ --kf-ink-soft: #1B2A22;
+ --kf-mono: 'JetBrains Mono', 'IBM Plex Mono', ui-monospace,
SFMono-Regular, Menlo, monospace;
+ --kf-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
sans-serif;
+ --kf-container: 1320px;
+ --kf-gutter: 56px;
+
+ position: relative;
+ min-height: 100vh;
+ background: var(--kf-green-dark);
+ color: var(--kf-cream-light);
+ font-family: var(--kf-sans);
+ overflow-x: hidden;
+
+ * {
+ box-sizing: border-box;
+ }
+}
+
+.kf-next__container {
+ max-width: var(--kf-container);
+ margin: 0 auto;
+ padding: 0 var(--kf-gutter);
+ width: 100%;
+}
+
+.kf-next__eyebrow {
+ display: inline-flex;
+ align-items: center;
+ gap: 12px;
+ font-family: var(--kf-mono);
+ font-size: 12px;
+ font-weight: 500;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: rgba(245, 239, 228, 0.7);
+ flex-wrap: wrap;
+}
+
+.kf-next__eyebrow-line {
+ display: inline-block;
+ width: 28px;
+ height: 1.5px;
+ background: var(--kf-yellow);
+ flex-shrink: 0;
+}
+
+.kf-next__ver-pill {
+ padding: 3px 8px;
+ border: 1px solid rgba(255, 210, 63, 0.4);
+ color: var(--kf-yellow);
+ border-radius: 3px;
+ letter-spacing: 0.08em;
+ flex-shrink: 0;
+ font-size: 11px;
+}
+
+.kf-next__accent {
+ color: var(--kf-yellow);
+}
+
+.kf-next__section-head {
+ max-width: 760px;
+ margin-bottom: 64px;
+}
+
+.kf-next__section-head .kf-next__eyebrow {
+ margin-bottom: 22px;
+}
+
+.kf-next__section-title {
+ font-family: var(--kf-mono);
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: -0.035em;
+ word-spacing: -0.18em;
+ line-height: 0.95;
+ color: var(--kf-cream-light);
+ font-size: clamp(36px, 4.2vw, 56px);
+ margin: 0 0 22px;
+}
+
+.kf-next__section-sub {
+ font-size: 15px;
+ line-height: 1.6;
+ word-spacing: -0.12em;
+ color: rgba(245, 239, 228, 0.85);
+ max-width: 620px;
+ margin: 0;
+}
+
+.kf-next__section {
+ position: relative;
+ padding: 72px 0;
+ overflow: hidden;
+}
+
+.kf-next__section-inner {
+ position: relative;
+ z-index: 5;
+}
+
+[data-reveal] {
+ opacity: 0;
+ transform: translateY(24px);
+ transition:
+ opacity 0.7s cubic-bezier(.2, .8, .2, 1),
+ transform 0.7s cubic-bezier(.2, .8, .2, 1);
+}
+
+[data-reveal].is-visible {
+ opacity: 1;
+ transform: none;
+}
+
+[data-reveal-delay='1'] {
+ transition-delay: 0.08s;
+}
+
+[data-reveal-delay='2'] {
+ transition-delay: 0.16s;
+}
+
+[data-reveal-delay='3'] {
+ transition-delay: 0.24s;
+}
+
+[data-reveal-delay='4'] {
+ transition-delay: 0.32s;
+}
+
+.kf-next__shape {
+ position: absolute;
+ pointer-events: none;
+ z-index: 1;
+}
+
+.kf-next__shape--diamond {
+ width: 16px;
+ height: 16px;
+ background: var(--kf-yellow);
+ transform: rotate(45deg);
+}
+
+.kf-next__shape--circle {
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: var(--kf-coral);
+}
+
+.kf-next__shape--ring {
+ width: 22px;
+ height: 22px;
+ border-radius: 50%;
+ border: 2px solid var(--kf-yellow);
+}
+
+.kf-next__shape--cross {
+ width: 18px;
+ height: 18px;
+ position: relative;
+}
+
+.kf-next__shape--cross::before,
+.kf-next__shape--cross::after {
+ content: '';
+ position: absolute;
+ background: var(--kf-cream-light);
+ inset: 0;
+}
+
+.kf-next__shape--cross::before {
+ width: 100%;
+ height: 2px;
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+.kf-next__shape--cross::after {
+ height: 100%;
+ width: 2px;
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.kf-next__hero {
+ position: relative;
+ background: var(--kf-green-dark);
+ overflow: hidden;
+ padding: 72px 0 72px;
+}
+
+.kf-next__hero-bg {
+ position: absolute;
+ inset: 0;
+ z-index: 0;
+ background:
+ radial-gradient(ellipse 60% 50% at 78% 22%, rgba(45, 223, 168, 0.18),
transparent 70%),
+ radial-gradient(ellipse 50% 70% at 0% 100%, rgba(255, 210, 63, 0.05),
transparent 70%);
+}
+
+.kf-next__hero-grid {
+ position: absolute;
+ inset: 0;
+ background-image: radial-gradient(rgba(245, 239, 228, 0.09) 1.2px,
transparent 1.2px);
+ background-size: 28px 28px;
+ mask-image: linear-gradient(180deg, black, black 75%, transparent);
+ -webkit-mask-image: linear-gradient(180deg, black, black 75%, transparent);
+}
+
+.kf-next__hero-eyebrow {
+ margin-bottom: 22px;
+ display: inline-flex;
+}
+
+.kf-next__hero-stack {
+ position: relative;
+ z-index: 5;
+ max-width: 1040px;
+}
+
+.kf-next__hero-title {
+ font-family: var(--kf-mono);
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: -0.035em;
+ word-spacing: -0.18em;
+ line-height: 0.92;
+ color: var(--kf-cream-light);
+ margin: 0;
+ font-size: clamp(40px, 4.4vw, 62px);
+}
+
+.kf-next__bolt-inline {
+ display: inline-block;
+ vertical-align: -0.12em;
+ margin: 0 0.06em 0 0.04em;
+}
+
+.kf-next__hero-sub {
+ font-size: 15px;
+ line-height: 1.5;
+ word-spacing: -0.12em;
+ color: rgba(245, 239, 228, 0.85);
+ margin: 18px 0 0;
+ max-width: 540px;
+ font-weight: 400;
+}
+
+.kf-next__hero-scroll {
+ margin-top: 36px;
+ padding-top: 22px;
+ border-top: 1px dashed rgba(245, 239, 228, 0.18);
+ display: inline-flex;
+ align-items: center;
+ gap: 14px;
+ font-family: var(--kf-mono);
+ font-size: 11px;
+ font-weight: 500;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: rgba(245, 239, 228, 0.55);
+}
+
+.kf-next__hero-arrow {
+ display: inline-block;
+ transform: translateY(1px);
+}
+
+.kf-next__section--glossary {
+ background: linear-gradient(180deg, var(--kf-green-darker) 0%,
var(--kf-green-darkest) 100%);
+ padding: 36px 0 180px;
+}
+
+.kf-next__section--glossary::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ z-index: 0;
+ background:
+ radial-gradient(ellipse 60% 50% at 12% 8%, rgba(45, 223, 168, 0.14),
transparent 70%),
+ radial-gradient(ellipse 50% 60% at 92% 96%, rgba(255, 210, 63, 0.04),
transparent 72%);
+ pointer-events: none;
+}
+
+.kf-next__glossary-controls {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ gap: 32px;
+ align-items: center;
+ margin-bottom: 36px;
+}
+
+.kf-next__search {
+ position: relative;
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ padding: 18px 22px;
+ background: rgba(7, 121, 90, 0.78);
+ border: 1px solid rgba(255, 210, 63, 0.62);
+ border-radius: 6px;
+ transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s
ease;
+ box-shadow: 0 14px 40px -24px rgba(0, 0, 0, 0.45);
+}
+
+.kf-next__search:focus-within {
+ border-color: var(--kf-yellow);
+ background: rgba(7, 121, 90, 0.92);
+ box-shadow:
+ 0 0 0 3px rgba(255, 210, 63, 0.22),
+ 0 0 0 1px rgba(255, 210, 63, 0.2) inset,
+ 0 18px 40px -18px rgba(0, 0, 0, 0.5);
+}
+
+.kf-next__search-icon {
+ color: rgba(255, 210, 63, 0.96);
+ flex-shrink: 0;
+}
+
+.kf-next__search:focus-within .kf-next__search-icon {
+ color: var(--kf-yellow);
+}
+
+.kf-next__search-input {
+ flex: 1;
+ background: transparent;
+ border: 0;
+ outline: none;
+ color: var(--kf-cream-light);
+ font-family: var(--kf-mono);
+ font-size: 16px;
+ letter-spacing: 0.01em;
+}
+
+.kf-next__search-input::placeholder {
+ color: rgba(245, 239, 228, 0.38);
+}
+
+.kf-next__search-input::-webkit-search-cancel-button,
+.kf-next__search-input::-webkit-search-decoration {
+ -webkit-appearance: none;
+ appearance: none;
+}
+
+.kf-next__search-clear {
+ background: transparent;
+ border: 0;
+ color: rgba(245, 239, 228, 0.6);
+ font-size: 22px;
+ line-height: 1;
+ width: 26px;
+ height: 26px;
+ border-radius: 50%;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+ transition: color 0.15s ease, background 0.15s ease;
+}
+
+.kf-next__search-clear:hover {
+ color: var(--kf-cream-light);
+ background: rgba(245, 239, 228, 0.08);
+}
+
+.kf-next__meta {
+ display: inline-flex;
+ align-items: baseline;
+ gap: 10px;
+ font-family: var(--kf-mono);
+ font-size: 12px;
+ font-weight: 500;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: rgba(245, 239, 228, 0.55);
+ white-space: nowrap;
+}
+
+.kf-next__meta-label {
+ letter-spacing: 0.24em;
+}
+
+.kf-next__meta-count strong {
+ color: var(--kf-yellow);
+ font-weight: 700;
+ font-size: 14px;
+}
+
+.kf-next__grid {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: grid;
+ grid-template-columns: repeat(12, 1fr);
+ grid-auto-rows: 96px;
+ grid-auto-flow: dense;
+ gap: 8px;
+}
+
+.kf-next__tile {
+ position: relative;
+ border-radius: 4px;
+ overflow: hidden;
+ transition:
+ transform 0.25s cubic-bezier(.2, .8, .2, 1),
+ box-shadow 0.25s cubic-bezier(.2, .8, .2, 1),
+ opacity 0.2s ease;
+ grid-column: span 3;
+ grid-row: span 1;
+}
+
+.kf-next__tile:hover {
+ transform: translateY(-6px);
+ box-shadow: 0 18px 30px -14px rgba(0, 0, 0, 0.45);
+ z-index: 2;
+}
+
+.kf-next__tile.is-hidden {
+ display: none;
+}
+
+.kf-next__tile.span-s {
+ grid-column: span 2;
+ grid-row: span 1;
+}
+
+.kf-next__tile.span-m {
+ grid-column: span 3;
+ grid-row: span 1;
+}
+
+.kf-next__tile.span-l {
+ grid-column: span 4;
+ grid-row: span 1;
+}
+
+.kf-next__tile.span-t {
+ grid-column: span 2;
+ grid-row: span 2;
+}
+
+.kf-next__tile-link {
+ position: relative;
+ display: grid;
+ grid-template-rows: auto minmax(0, 1fr) auto;
+ width: 100%;
+ height: 100%;
+ padding: 16px 18px 14px;
+ text-decoration: none;
+ color: inherit;
+ gap: 10px;
+}
+
+.kf-next__tile-badge-row {
+ display: flex;
+ justify-content: flex-end;
+ min-height: 22px;
+}
+
+.kf-next__tile-top {
+ min-width: 0;
+ min-height: 0;
+}
+
+.kf-next__tile-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 34px;
+ height: 22px;
+ padding: 0 8px;
+ border-radius: 999px;
+ border: 1px solid rgba(245, 239, 228, 0.24);
+ background: rgba(15, 26, 20, 0.24);
+ color: var(--kf-cream-light);
+ font-family: var(--kf-mono);
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: 0.12em;
+ text-transform: none;
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ pointer-events: none;
+}
+
+.kf-next__tile-name {
+ display: block;
+ font-family: var(--kf-mono);
+ font-weight: 700;
+ font-size: clamp(13px, 1.05vw, 16px);
+ line-height: 1.06;
+ letter-spacing: -0.005em;
+ word-spacing: -0.08em;
+ text-transform: none;
+ overflow-wrap: anywhere;
+ hyphens: auto;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+.kf-next__tile-meta {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+ gap: 10px;
+ min-width: 0;
+ align-self: end;
+}
+
+.kf-next__tile-tags {
+ flex: 1 1 auto;
+ font-family: var(--kf-mono);
+ font-size: clamp(10px, 0.8vw, 11px);
+ letter-spacing: 0.06em;
+ word-spacing: -0.08em;
+ opacity: 0.7;
+ min-width: 0;
+ overflow-wrap: anywhere;
+}
+
+.kf-next__tile-arrow {
+ flex: 0 0 auto;
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-family: var(--kf-mono);
+ font-size: 13px;
+ background: rgba(15, 26, 20, 0.08);
+ transition:
+ transform 0.25s cubic-bezier(.2, .8, .2, 1),
+ background 0.25s ease,
+ color 0.25s ease;
+}
+
+.kf-next__tile:hover .kf-next__tile-arrow {
+ transform: rotate(-45deg);
+ background: var(--kf-yellow);
+ color: var(--kf-ink);
+}
+
+.kf-next__tile.t-green {
+ background: linear-gradient(180deg, #18ae83 0%, #129a72 100%);
+ color: var(--kf-cream-light);
+}
+
+.kf-next__tile.t-green .kf-next__tile-arrow {
+ background: rgba(245, 239, 228, 0.2);
+ color: var(--kf-cream-light);
+}
+
+.kf-next__tile.t-cream {
+ background: var(--kf-cream-light);
+ color: var(--kf-ink);
+}
+
+.kf-next__tile.t-cream .kf-next__tile-arrow {
+ background: rgba(15, 26, 20, 0.08);
+ color: var(--kf-ink);
+}
+
+.kf-next__tile.t-green-deep {
+ background: linear-gradient(180deg, #0b7a58 0%, #086a4d 100%);
+ color: var(--kf-cream-light);
+}
+
+.kf-next__tile.t-green-deep .kf-next__tile-arrow {
+ background: rgba(245, 239, 228, 0.18);
+ color: var(--kf-cream-light);
+}
+
+.kf-next__tile.t-cream-warm {
+ background: var(--kf-cream-warm);
+ color: var(--kf-ink);
+}
+
+.kf-next__tile.t-cream-warm .kf-next__tile-arrow {
+ background: rgba(15, 26, 20, 0.08);
+ color: var(--kf-ink);
+}
+
+.kf-next__tile.span-t .kf-next__tile-link::after {
+ content: none;
+}
+
+@media (max-width: 1100px) {
+ .kf-next {
+ --kf-gutter: 36px;
+ }
+
+ .kf-next__grid {
+ grid-template-columns: repeat(8, 1fr);
+ grid-auto-rows: 88px;
+ }
+
+ .kf-next__tile.span-s {
+ grid-column: span 2;
+ }
+
+ .kf-next__tile.span-m {
+ grid-column: span 3;
+ }
+
+ .kf-next__tile.span-l {
+ grid-column: span 4;
+ }
+
+ .kf-next__tile.span-t {
+ grid-column: span 2;
+ grid-row: span 2;
+ }
+}
+
+@media (max-width: 700px) {
+ .kf-next {
+ --kf-gutter: 22px;
+ }
+
+ .kf-next__hero {
+ padding: 56px 0 56px;
+ }
+
+ .kf-next__section {
+ padding: 56px 0;
+ }
+
+ .kf-next__glossary-controls {
+ grid-template-columns: 1fr;
+ gap: 16px;
+ }
+
+ .kf-next__grid {
+ grid-template-columns: repeat(4, 1fr);
+ grid-auto-rows: 78px;
+ }
+
+ .kf-next__tile.span-s,
+ .kf-next__tile.span-m {
+ grid-column: span 2;
+ grid-row: span 1;
+ }
+
+ .kf-next__tile.span-l {
+ grid-column: span 4;
+ grid-row: span 1;
+ }
+
+ .kf-next__tile.span-t {
+ grid-column: span 4;
+ grid-row: span 1;
+ }
+
+ .kf-next__tile-link {
+ padding: 14px 16px 12px;
+ gap: 8px;
+ }
+
+ .kf-next__tile-badge-row {
+ min-height: 20px;
+ }
+
+ .kf-next__tile-badge {
+ min-width: 30px;
+ height: 20px;
+ font-size: 9px;
+ }
+
+ .kf-next__tile-meta {
+ gap: 8px;
+ }
+}
diff --git a/src/components/why-doris-next/KeyFeaturesNext.tsx
b/src/components/why-doris-next/KeyFeaturesNext.tsx
new file mode 100644
index 00000000000..abc3fdcad55
--- /dev/null
+++ b/src/components/why-doris-next/KeyFeaturesNext.tsx
@@ -0,0 +1,250 @@
+import React, { JSX, useEffect, useId, useMemo, useState } from 'react';
+import Link from '@docusaurus/Link';
+import { LayoutNext } from '../home-next/LayoutNext';
+import { keyFeatureCards, type KeyFeatureCard } from
'@site/src/generated/key-features';
+import './KeyFeaturesNext.scss';
+
+type CardSpan = 's' | 'm' | 'l' | 't';
+
+const CARD_SPANS: CardSpan[] = ['s', 'm', 'l', 't'];
+
+function randomSpan(): CardSpan {
+ return CARD_SPANS[Math.floor(Math.random() * CARD_SPANS.length)];
+}
+
+function useRevealObserver(): void {
+ useEffect(() => {
+ const items = document.querySelectorAll<HTMLElement>('[data-reveal]');
+ if (!('IntersectionObserver' in window)) {
+ items.forEach((item) => item.classList.add('is-visible'));
+ return undefined;
+ }
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ entry.target.classList.add('is-visible');
+ observer.unobserve(entry.target);
+ }
+ });
+ },
+ { threshold: 0.12, rootMargin: '0px 0px -8% 0px' }
+ );
+
+ items.forEach((item) => observer.observe(item));
+ return () => observer.disconnect();
+ }, []);
+}
+
+function BoltIcon({ size = 24, color = '#FFD23F' }: { size?: number | string;
color?: string }) {
+ return (
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none"
aria-hidden="true">
+ <path
+ d="M13 2L3 14h7l-1 8 11-13h-7l1-7z"
+ fill={color}
+ stroke={color}
+ strokeWidth="0.5"
+ strokeLinejoin="round"
+ />
+ </svg>
+ );
+}
+
+function matches(entry: KeyFeatureCard, query: string): boolean {
+ if (!query) return true;
+ const needle = query.trim().toLowerCase();
+ if (!needle) return true;
+ if (entry.title.toLowerCase().includes(needle)) return true;
+ if (entry.description.toLowerCase().includes(needle)) return true;
+ if (entry.tags?.some((tag) => tag.toLowerCase().includes(needle))) return
true;
+ return false;
+}
+
+function themeFor(index: number): string {
+ const themes = ['t-green', 't-cream', 't-green-deep', 't-cream-warm'];
+ return themes[index % themes.length];
+}
+
+function KeyFeaturesHero(): JSX.Element {
+ return (
+ <section className="kf-next__hero" id="hero">
+ <div className="kf-next__hero-bg" aria-hidden="true" />
+ <div className="kf-next__hero-grid" aria-hidden="true" />
+ <div className="kf-next__container">
+ <div className="kf-next__hero-stack">
+ <h1 className="kf-next__hero-title" data-reveal
data-reveal-delay="1">
+ Everything you need to know
+ <br />
+ about{' '}
+ <span className="kf-next__accent">
+ Apache Doris
+ <span className="kf-next__bolt-inline">
+ <BoltIcon size="0.85em" />
+ </span>
+ </span>
+ </h1>
+ <p className="kf-next__hero-sub" data-reveal
data-reveal-delay="2">
+ A reference index for the core technologies and key
features of Apache Doris
+ </p>
+ </div>
+ </div>
+ </section>
+ );
+}
+
+function GlossarySection(): JSX.Element {
+ const [query, setQuery] = useState('');
+ const [spanById, setSpanById] = useState<Record<string, CardSpan>>({});
+ const inputId = useId();
+ const liveId = useId();
+
+ useEffect(() => {
+ const next: Record<string, CardSpan> = {};
+ keyFeatureCards.forEach((entry) => {
+ next[entry.id] = randomSpan();
+ });
+ setSpanById(next);
+ }, []);
+
+ const filtered = useMemo(
+ () =>
+ keyFeatureCards.map((entry, index) => ({
+ entry,
+ originalIdx: index,
+ visible: matches(entry, query),
+ })),
+ [query]
+ );
+
+ const visibleCount = filtered.filter((item) => item.visible).length;
+
+ return (
+ <section className="kf-next__section kf-next__section--glossary"
id="glossary">
+ <div className="kf-next__container kf-next__section-inner">
+ <div className="kf-next__glossary-controls" data-reveal>
+ <label className="kf-next__search" htmlFor={inputId}>
+ <svg
+ className="kf-next__search-icon"
+ width="18"
+ height="18"
+ viewBox="0 0 24 24"
+ fill="none"
+ aria-hidden="true"
+ >
+ <circle cx="10.5" cy="10.5" r="6.5"
stroke="currentColor" strokeWidth="1.8" />
+ <path
+ d="M15.5 15.5L20 20"
+ stroke="currentColor"
+ strokeWidth="1.8"
+ strokeLinecap="round"
+ />
+ </svg>
+ <input
+ id={inputId}
+ type="search"
+ className="kf-next__search-input"
+ placeholder="Search features - try “lakehouse”,
“MPP”, “index”…"
+ value={query}
+ onChange={(event) => setQuery(event.target.value)}
+ autoComplete="off"
+ spellCheck={false}
+ aria-describedby={liveId}
+ />
+ {query && (
+ <button
+ type="button"
+ className="kf-next__search-clear"
+ onClick={() => setQuery('')}
+ aria-label="Clear search"
+ >
+ ×
+ </button>
+ )}
+ </label>
+
+ <div className="kf-next__meta" id={liveId}
aria-live="polite">
+ <span className="kf-next__meta-count">
+ <strong>{String(visibleCount).padStart(2,
'0')}</strong>
+ {' / '}
+ {String(keyFeatureCards.length).padStart(2, '0')}
+ </span>
+ <span className="kf-next__meta-label">features</span>
+ </div>
+ </div>
+
+ <ul className="kf-next__grid" data-reveal
data-reveal-delay="1">
+ {filtered.map(({ entry, originalIdx, visible }) => (
+ <li
+ key={entry.id}
+ className={[
+ 'kf-next__tile',
+ themeFor(originalIdx),
+ `span-${spanById[entry.id] ?? 'm'}`,
+ visible ? '' : 'is-hidden',
+ ]
+ .filter(Boolean)
+ .join(' ')}
+ aria-hidden={visible ? 'false' : 'true'}
+ >
+ <Link className="kf-next__tile-link"
to={entry.href}>
+ {entry.badge === 'doc' && (
+ <span className="kf-next__tile-badge-row">
+ <span className="kf-next__tile-badge"
aria-label="Formal documentation">
+ Doc
+ </span>
+ </span>
+ )}
+ <span className="kf-next__tile-top">
+ <span
className="kf-next__tile-name">{entry.title}</span>
+ </span>
+ <span className="kf-next__tile-meta">
+ {entry.tags && entry.tags.length > 0 && (
+ <span className="kf-next__tile-tags">
+ {entry.tags.slice(0, 2).join(' ·
')}
+ </span>
+ )}
+ <span className="kf-next__tile-arrow"
aria-hidden="true">
+ →
+ </span>
+ </span>
+ </Link>
+ </li>
+ ))}
+ </ul>
+
+ {visibleCount === 0 && (
+ <div className="kf-next__empty" role="status">
+ No features match <strong>“{query}”</strong>. Try a
broader term, or{' '}
+ <button type="button" className="kf-next__empty-clear"
onClick={() => setQuery('')}>
+ clear the search
+ </button>
+ .
+ </div>
+ )}
+ </div>
+ </section>
+ );
+}
+
+function KeyFeaturesContent(): JSX.Element {
+ useRevealObserver();
+
+ return (
+ <div className="kf-next">
+ <KeyFeaturesHero />
+ <GlossarySection />
+ </div>
+ );
+}
+
+export default function KeyFeaturesNext(): JSX.Element {
+ return (
+ <LayoutNext
+ title="Key Features"
+ description="A reference index for the core technologies and key
features of Apache Doris, including real-time analytics, lakehouse federation,
and hybrid search."
+ >
+ <KeyFeaturesContent />
+ </LayoutNext>
+ );
+}
diff --git a/src/pages/why-doris/key-features/index.tsx
b/src/pages/why-doris/key-features/index.tsx
new file mode 100644
index 00000000000..7f4551c89f3
--- /dev/null
+++ b/src/pages/why-doris/key-features/index.tsx
@@ -0,0 +1,6 @@
+import React, { JSX } from 'react';
+import KeyFeaturesNext from
'@site/src/components/why-doris-next/KeyFeaturesNext';
+
+export default function KeyFeaturesPage(): JSX.Element {
+ return <KeyFeaturesNext />;
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]