docs(seo): add structured data, OpenGraph tags, and sitemap improvements (#37404)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Evan Rusackas
2026-02-06 14:09:19 -05:00
committed by GitHub
parent a04571fa20
commit 46bca32677
7 changed files with 589 additions and 3 deletions

View File

@@ -1,7 +1,63 @@
---
sidebar_position: 9
title: Frequently Asked Questions
description: Common questions about Apache Superset including performance, database support, visualizations, and configuration.
keywords: [superset faq, superset questions, superset help, data visualization faq]
---
import FAQSchema from '@site/src/components/FAQSchema';
<FAQSchema faqs={[
{
question: "How big of a dataset can Superset handle?",
answer: "Superset can work with even gigantic databases. Superset acts as a thin layer above your underlying databases or data engines, which do all the processing. Superset simply visualizes the results of the query. The key to achieving acceptable performance is whether your database can execute queries and return results at acceptable speed."
},
{
question: "What are the computing specifications required to run Superset?",
answer: "The specs depend on how many users you have and their activity, not on the size of your data. Community members have reported 8GB RAM, 2vCPUs as adequate for a moderately-sized instance. Monitor your resource usage and adjust as needed."
},
{
question: "Can I join or query multiple tables at one time?",
answer: "Not in the Explore or Visualization UI directly. A Superset SQLAlchemy datasource can only be a single table or a view. You can create a view that joins tables, or use SQL Lab where you can write SQL queries to join multiple tables."
},
{
question: "How do I create my own visualization?",
answer: "Read the instructions in the Creating Visualization Plugins documentation to learn how to build custom visualizations for Superset."
},
{
question: "Can I upload and visualize CSV data?",
answer: "Yes! Superset supports CSV upload functionality. Read the Exploring Data documentation to learn how to enable and use CSV upload."
},
{
question: "Why are my queries timing out?",
answer: "There are many possible causes. For SQL Lab, Superset allows queries to run up to 6 hours by default (configurable via SQLLAB_ASYNC_TIME_LIMIT_SEC). For dashboard timeouts, check your gateway/proxy timeout settings and adjust SUPERSET_WEBSERVER_TIMEOUT in superset_config.py."
},
{
question: "Why is the map not visible in the geospatial visualization?",
answer: "You need to register a free account at Mapbox.com, obtain an API key, and add it to your .env file at the key MAPBOX_API_KEY."
},
{
question: "What database engine can I use as a backend for Superset?",
answer: "Superset is tested using MySQL, PostgreSQL, and SQLite backends for storing its internal metadata. While Superset supports many databases as data sources, only these are recommended for the metadata store in production."
},
{
question: "Does Superset work with my database?",
answer: "Superset supports any database with a Python SQLAlchemy dialect and DBAPI driver. Check the Connecting to Databases documentation for the full list of supported databases."
},
{
question: "Does Superset offer a public API?",
answer: "Yes, Superset has a public REST API documented using Swagger. Enable FAB_API_SWAGGER_UI in superset_config.py to access interactive API documentation at /swagger/v1."
},
{
question: "Does Superset collect any telemetry data?",
answer: "Superset uses Scarf by default to collect basic telemetry data to help maintainers understand version usage. Users can opt out by setting the SCARF_ANALYTICS environment variable to false."
},
{
question: "Does Superset have a trash bin to recover deleted assets?",
answer: "No, there is no built-in way to recover deleted dashboards, charts, or datasets. It is recommended to take periodic backups of the metadata database and use export functionality for recovery."
}
]} />
# FAQ
## How big of a dataset can Superset handle?

View File

@@ -23,6 +23,7 @@ import type * as OpenApiPlugin from 'docusaurus-plugin-openapi-docs';
import { themes } from 'prism-react-renderer';
import remarkImportPartial from 'remark-import-partial';
import remarkLocalizeBadges from './plugins/remark-localize-badges.mjs';
import remarkTechArticleSchema from './plugins/remark-tech-article-schema.mjs';
import * as fs from 'fs';
import * as path from 'path';
@@ -46,7 +47,7 @@ if (!versionsConfig.components.disabled) {
sidebarPath: require.resolve('./sidebarComponents.js'),
editUrl:
'https://github.com/apache/superset/edit/master/docs/components',
remarkPlugins: [remarkImportPartial, remarkLocalizeBadges],
remarkPlugins: [remarkImportPartial, remarkLocalizeBadges, remarkTechArticleSchema],
admonitions: {
keywords: ['note', 'tip', 'info', 'warning', 'danger', 'resources'],
extendDefaults: true,
@@ -74,7 +75,7 @@ if (!versionsConfig.developer_portal.disabled) {
sidebarPath: require.resolve('./sidebarTutorials.js'),
editUrl:
'https://github.com/apache/superset/edit/master/docs/developer_portal',
remarkPlugins: [remarkImportPartial, remarkLocalizeBadges],
remarkPlugins: [remarkImportPartial, remarkLocalizeBadges, remarkTechArticleSchema],
admonitions: {
keywords: ['note', 'tip', 'info', 'warning', 'danger', 'resources'],
extendDefaults: true,
@@ -180,6 +181,83 @@ const config: Config = {
favicon: '/img/favicon.ico',
organizationName: 'apache',
projectName: 'superset',
// SEO: Structured data (Organization, Software, WebSite with SearchAction)
headTags: [
// SoftwareApplication schema
{
tagName: 'script',
attributes: {
type: 'application/ld+json',
},
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: 'Apache Superset',
applicationCategory: 'BusinessApplication',
operatingSystem: 'Cross-platform',
description: 'Apache Superset is a modern, enterprise-ready business intelligence web application for data exploration and visualization.',
url: 'https://superset.apache.org',
license: 'https://www.apache.org/licenses/LICENSE-2.0',
author: {
'@type': 'Organization',
name: 'Apache Software Foundation',
url: 'https://www.apache.org/',
logo: 'https://www.apache.org/foundation/press/kit/asf_logo.png',
},
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'USD',
},
featureList: [
'Interactive dashboards',
'SQL IDE',
'40+ visualization types',
'Semantic layer',
'Role-based access control',
'REST API',
],
}),
},
// WebSite schema with SearchAction (enables sitelinks search box in Google)
{
tagName: 'script',
attributes: {
type: 'application/ld+json',
},
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'Apache Superset',
url: 'https://superset.apache.org',
potentialAction: {
'@type': 'SearchAction',
target: {
'@type': 'EntryPoint',
urlTemplate: 'https://superset.apache.org/search?q={search_term_string}',
},
'query-input': 'required name=search_term_string',
},
}),
},
// Preconnect hints for faster external resource loading
{
tagName: 'link',
attributes: {
rel: 'preconnect',
href: 'https://WR5FASX5ED-dsn.algolia.net',
crossorigin: 'anonymous',
},
},
{
tagName: 'link',
attributes: {
rel: 'preconnect',
href: 'https://analytics.apache.org',
},
},
],
themes: [
'@saucelabs/theme-github-codeblock',
'@docusaurus/theme-mermaid',
@@ -212,6 +290,19 @@ const config: Config = {
},
},
],
// SEO: Generate robots.txt during build
[
require.resolve('./plugins/robots-txt-plugin.js'),
{
policies: [
{
userAgent: '*',
allow: '/',
disallow: ['/api/v1/', '/_next/', '/static/js/*.map'],
},
],
},
],
[
'@docusaurus/plugin-client-redirects',
{
@@ -373,7 +464,7 @@ const config: Config = {
}
return `https://github.com/apache/superset/edit/master/docs/${versionDocsDirPath}/${docPath}`;
},
remarkPlugins: [remarkImportPartial, remarkLocalizeBadges],
remarkPlugins: [remarkImportPartial, remarkLocalizeBadges, remarkTechArticleSchema],
admonitions: {
keywords: ['note', 'tip', 'info', 'warning', 'danger', 'resources'],
extendDefaults: true,
@@ -396,11 +487,57 @@ const config: Config = {
theme: {
customCss: require.resolve('./src/styles/custom.css'),
},
// SEO: Sitemap configuration with priorities
sitemap: {
lastmod: 'date',
changefreq: 'weekly',
priority: 0.5,
ignorePatterns: ['/tags/**'],
filename: 'sitemap.xml',
createSitemapItems: async (params) => {
const { defaultCreateSitemapItems, ...rest } = params;
const items = await defaultCreateSitemapItems(rest);
return items.map((item) => {
// Boost priority for key pages
if (item.url.includes('/docs/intro')) {
return { ...item, priority: 1.0, changefreq: 'daily' };
}
if (item.url.includes('/docs/quickstart')) {
return { ...item, priority: 0.9, changefreq: 'weekly' };
}
if (item.url.includes('/docs/installation/')) {
return { ...item, priority: 0.8, changefreq: 'weekly' };
}
if (item.url.includes('/docs/databases')) {
return { ...item, priority: 0.8, changefreq: 'weekly' };
}
if (item.url.includes('/docs/faq')) {
return { ...item, priority: 0.7, changefreq: 'monthly' };
}
if (item.url === 'https://superset.apache.org/') {
return { ...item, priority: 1.0, changefreq: 'daily' };
}
return item;
});
},
},
} satisfies Options,
],
],
themeConfig: {
// SEO: OpenGraph and Twitter meta tags
metadata: [
{ name: 'keywords', content: 'data visualization, business intelligence, BI, dashboards, SQL, analytics, open source, Apache, charts, reporting' },
{ property: 'og:type', content: 'website' },
{ property: 'og:site_name', content: 'Apache Superset' },
{ property: 'og:image', content: 'https://superset.apache.org/img/superset-og-image.png' },
{ property: 'og:image:width', content: '1200' },
{ property: 'og:image:height', content: '630' },
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:image', content: 'https://superset.apache.org/img/superset-og-image.png' },
{ name: 'twitter:site', content: '@ApacheSuperset' },
],
colorMode: {
defaultMode: 'dark',
disableSwitch: false,

View File

@@ -0,0 +1,153 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// Note: visit from unist-util-visit is available if needed for tree traversal
/**
* Remark plugin that automatically injects TechArticle schema import and component
* into documentation MDX files based on frontmatter.
*
* This enables rich snippets for technical documentation in search results.
*
* Frontmatter options:
* - title: (required) Article headline
* - description: (required) Article description
* - keywords: (optional) Array of keywords
* - seo_proficiency: (optional) 'Beginner' or 'Expert', defaults to 'Beginner'
* - seo_schema: (optional) Set to false to disable schema injection
*/
export default function remarkTechArticleSchema() {
return (tree, file) => {
const frontmatter = file.data.frontMatter || {};
// Skip if explicitly disabled or missing required fields
if (frontmatter.seo_schema === false) {
return;
}
// Only add schema if we have title and description
if (!frontmatter.title || !frontmatter.description) {
return;
}
const title = frontmatter.title;
const description = frontmatter.description;
const keywords = Array.isArray(frontmatter.keywords) ? frontmatter.keywords : [];
const proficiencyLevel = frontmatter.seo_proficiency || 'Beginner';
// Create the import statement
const importNode = {
type: 'mdxjsEsm',
value: `import TechArticleSchema from '@site/src/components/TechArticleSchema';`,
data: {
estree: {
type: 'Program',
sourceType: 'module',
body: [
{
type: 'ImportDeclaration',
specifiers: [
{
type: 'ImportDefaultSpecifier',
local: { type: 'Identifier', name: 'TechArticleSchema' },
},
],
source: {
type: 'Literal',
value: '@site/src/components/TechArticleSchema',
},
},
],
},
},
};
// Create the component node for MDX
const componentNode = {
type: 'mdxJsxFlowElement',
name: 'TechArticleSchema',
attributes: [
{
type: 'mdxJsxAttribute',
name: 'title',
value: title,
},
{
type: 'mdxJsxAttribute',
name: 'description',
value: description,
},
...(keywords.length > 0
? [
{
type: 'mdxJsxAttribute',
name: 'keywords',
value: {
type: 'mdxJsxAttributeValueExpression',
value: JSON.stringify(keywords),
data: {
estree: {
type: 'Program',
sourceType: 'module',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'ArrayExpression',
elements: keywords.map((k) => ({
type: 'Literal',
value: k,
})),
},
},
],
},
},
},
},
]
: []),
...(proficiencyLevel !== 'Beginner'
? [
{
type: 'mdxJsxAttribute',
name: 'proficiencyLevel',
value: proficiencyLevel,
},
]
: []),
],
children: [],
};
// Insert import at the beginning
tree.children.unshift(importNode);
// Find the first heading and insert component after it
let insertIndex = 1; // Default: after import
for (let i = 1; i < tree.children.length; i++) {
if (tree.children[i].type === 'heading') {
insertIndex = i + 1;
break;
}
}
tree.children.splice(insertIndex, 0, componentNode);
};
}

View File

@@ -0,0 +1,83 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint-disable @typescript-eslint/no-require-imports */
const fs = require('fs');
const path = require('path');
/* eslint-enable @typescript-eslint/no-require-imports */
/**
* Docusaurus plugin to generate robots.txt during build
* Configuration is passed via plugin options
*/
module.exports = function robotsTxtPlugin(context, options = {}) {
const { siteConfig } = context;
const {
policies = [{ userAgent: '*', allow: '/' }],
additionalSitemaps = [],
} = options;
return {
name: 'robots-txt-plugin',
async postBuild({ outDir }) {
const sitemapUrl = `${siteConfig.url}/sitemap.xml`;
// Build robots.txt content
const lines = [];
// Add policies
for (const policy of policies) {
lines.push(`User-agent: ${policy.userAgent}`);
if (policy.allow) {
const allows = Array.isArray(policy.allow) ? policy.allow : [policy.allow];
for (const allow of allows) {
lines.push(`Allow: ${allow}`);
}
}
if (policy.disallow) {
const disallows = Array.isArray(policy.disallow) ? policy.disallow : [policy.disallow];
for (const disallow of disallows) {
lines.push(`Disallow: ${disallow}`);
}
}
if (policy.crawlDelay) {
lines.push(`Crawl-delay: ${policy.crawlDelay}`);
}
lines.push(''); // Empty line between policies
}
// Add sitemaps
lines.push(`Sitemap: ${sitemapUrl}`);
for (const sitemap of additionalSitemaps) {
lines.push(`Sitemap: ${sitemap}`);
}
// Write robots.txt
const robotsPath = path.join(outDir, 'robots.txt');
fs.writeFileSync(robotsPath, lines.join('\n'));
console.log('Generated robots.txt');
},
};
};

View File

@@ -0,0 +1,66 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import type { JSX } from 'react';
import Head from '@docusaurus/Head';
interface FAQItem {
question: string;
answer: string;
}
interface FAQSchemaProps {
faqs: FAQItem[];
}
/**
* Component that injects FAQPage JSON-LD structured data
* Use this on FAQ pages to enable rich snippets in search results
*
* @example
* <FAQSchema faqs={[
* { question: "What is Superset?", answer: "Apache Superset is..." },
* { question: "How do I install it?", answer: "You can install via..." }
* ]} />
*/
export default function FAQSchema({ faqs }: FAQSchemaProps): JSX.Element | null {
// FAQPage schema requires a non-empty mainEntity array per schema.org specs
if (!faqs || faqs.length === 0) {
return null;
}
const schema = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: faqs.map((faq) => ({
'@type': 'Question',
name: faq.question,
acceptedAnswer: {
'@type': 'Answer',
text: faq.answer,
},
})),
};
return (
<Head>
<script type="application/ld+json">{JSON.stringify(schema)}</script>
</Head>
);
}

View File

@@ -0,0 +1,91 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import type { JSX } from 'react';
import Head from '@docusaurus/Head';
import { useLocation } from '@docusaurus/router';
interface TechArticleSchemaProps {
title: string;
description: string;
datePublished?: string;
dateModified?: string;
keywords?: string[];
proficiencyLevel?: 'Beginner' | 'Expert';
}
/**
* Component that injects TechArticle JSON-LD structured data for documentation pages.
* This helps search engines understand technical documentation content.
*
* @example
* <TechArticleSchema
* title="Installing Superset with Docker"
* description="Learn how to install Apache Superset using Docker Compose"
* keywords={['docker', 'installation', 'superset']}
* proficiencyLevel="Beginner"
* />
*/
export default function TechArticleSchema({
title,
description,
datePublished,
dateModified,
keywords = [],
proficiencyLevel = 'Beginner',
}: TechArticleSchemaProps): JSX.Element {
const location = useLocation();
const url = `https://superset.apache.org${location.pathname}`;
const schema = {
'@context': 'https://schema.org',
'@type': 'TechArticle',
headline: title,
description,
url,
proficiencyLevel,
author: {
'@type': 'Organization',
name: 'Apache Superset Contributors',
url: 'https://github.com/apache/superset/graphs/contributors',
},
publisher: {
'@type': 'Organization',
name: 'Apache Software Foundation',
url: 'https://www.apache.org/',
logo: {
'@type': 'ImageObject',
url: 'https://www.apache.org/foundation/press/kit/asf_logo.png',
},
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': url,
},
...(datePublished && { datePublished }),
...(dateModified && { dateModified }),
...(keywords.length > 0 && { keywords: keywords.join(', ') }),
};
return (
<Head>
<script type="application/ld+json">{JSON.stringify(schema)}</script>
</Head>
);
}

BIN
docs/static/img/superset-og-image.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB