From a1c75d42453180b7249b4617fe7a52b8f84dbebc Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Mon, 26 Jan 2026 09:34:55 -0800 Subject: [PATCH 1/3] feat(seo): add robots.txt, sitemap, JSON-LD, and enhanced metadata Marketing site (superset.sh): - Add robots.ts with crawl directives - Add sitemap.ts with dynamic blog post URLs - Update layout.tsx with full Open Graph, Twitter Cards, keywords - Add JSON-LD structured data (Organization, SoftwareApplication, Article, Website) - Add opengraph-image.tsx for dynamic OG image generation - Add RSS feed at /feed.xml - Add manifest.json for PWA support - Update blog posts with Article JSON-LD and canonical URLs Docs site (docs.superset.sh): - Add robots.ts with crawl directives - Add sitemap.ts from Fumadocs source - Update layout.tsx with metadataBase and full metadata --- apps/docs/src/app/layout.tsx | 44 +++++- apps/docs/src/app/robots.ts | 14 ++ apps/docs/src/app/sitemap.ts | 15 ++ apps/marketing/public/manifest.json | 16 ++ apps/marketing/src/app/blog/[slug]/page.tsx | 18 +++ apps/marketing/src/app/feed.xml/route.ts | 46 ++++++ apps/marketing/src/app/layout.tsx | 65 +++++++- apps/marketing/src/app/opengraph-image.tsx | 90 +++++++++++ apps/marketing/src/app/robots.ts | 14 ++ apps/marketing/src/app/sitemap.ts | 49 ++++++ .../src/components/JsonLd/JsonLd.tsx | 148 ++++++++++++++++++ apps/marketing/src/components/JsonLd/index.ts | 6 + 12 files changed, 523 insertions(+), 2 deletions(-) create mode 100644 apps/docs/src/app/robots.ts create mode 100644 apps/docs/src/app/sitemap.ts create mode 100644 apps/marketing/public/manifest.json create mode 100644 apps/marketing/src/app/feed.xml/route.ts create mode 100644 apps/marketing/src/app/opengraph-image.tsx create mode 100644 apps/marketing/src/app/robots.ts create mode 100644 apps/marketing/src/app/sitemap.ts create mode 100644 apps/marketing/src/components/JsonLd/JsonLd.tsx create mode 100644 apps/marketing/src/components/JsonLd/index.ts diff --git a/apps/docs/src/app/layout.tsx b/apps/docs/src/app/layout.tsx index 7c9d2ce80e4..e8e0b29b89e 100644 --- a/apps/docs/src/app/layout.tsx +++ b/apps/docs/src/app/layout.tsx @@ -10,12 +10,54 @@ const inter = Inter({ }); export const metadata: Metadata = { - title: "Superset Docs", + metadataBase: new URL("https://docs.superset.sh"), + title: { + default: "Superset Documentation", + template: "%s | Superset Docs", + }, + description: + "Official documentation for Superset - the terminal for coding agents. Learn how to run parallel coding agents on your machine.", + keywords: [ + "Superset documentation", + "coding agents docs", + "parallel execution guide", + "developer tools", + ], + authors: [{ name: "Superset Team" }], + creator: "Superset", + openGraph: { + type: "website", + locale: "en_US", + url: "https://docs.superset.sh", + siteName: "Superset Docs", + title: "Superset Documentation", + description: + "Official documentation for Superset - the terminal for coding agents.", + }, + twitter: { + card: "summary_large_image", + title: "Superset Documentation", + description: + "Official documentation for Superset - the terminal for coding agents.", + creator: "@AviSupersetSH", + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + "max-video-preview": -1, + "max-image-preview": "large", + "max-snippet": -1, + }, + }, icons: { icon: [ { url: "/favicon.ico", sizes: "32x32" }, { url: "/favicon-192.png", sizes: "192x192", type: "image/png" }, ], + apple: [{ url: "/apple-touch-icon.png", sizes: "180x180" }], }, }; diff --git a/apps/docs/src/app/robots.ts b/apps/docs/src/app/robots.ts new file mode 100644 index 00000000000..66dcd825978 --- /dev/null +++ b/apps/docs/src/app/robots.ts @@ -0,0 +1,14 @@ +import type { MetadataRoute } from "next"; + +export default function robots(): MetadataRoute.Robots { + return { + rules: [ + { + userAgent: "*", + allow: "/", + disallow: ["/api/", "/_next/"], + }, + ], + sitemap: "https://docs.superset.sh/sitemap.xml", + }; +} diff --git a/apps/docs/src/app/sitemap.ts b/apps/docs/src/app/sitemap.ts new file mode 100644 index 00000000000..8dae750af16 --- /dev/null +++ b/apps/docs/src/app/sitemap.ts @@ -0,0 +1,15 @@ +import type { MetadataRoute } from "next"; +import { source } from "@/lib/source"; + +export default function sitemap(): MetadataRoute.Sitemap { + const baseUrl = "https://docs.superset.sh"; + + const pages = source.getPages(); + + return pages.map((page) => ({ + url: `${baseUrl}${page.url}`, + lastModified: new Date(), + changeFrequency: "weekly" as const, + priority: page.url === "/quick-start" ? 1.0 : 0.8, + })); +} diff --git a/apps/marketing/public/manifest.json b/apps/marketing/public/manifest.json new file mode 100644 index 00000000000..49763c179ad --- /dev/null +++ b/apps/marketing/public/manifest.json @@ -0,0 +1,16 @@ +{ + "name": "Superset", + "short_name": "Superset", + "description": "Run 10+ parallel coding agents on your machine", + "start_url": "/", + "display": "standalone", + "background_color": "#0a0a0a", + "theme_color": "#0a0a0a", + "icons": [ + { + "src": "/favicon-192.png", + "sizes": "192x192", + "type": "image/png" + } + ] +} diff --git a/apps/marketing/src/app/blog/[slug]/page.tsx b/apps/marketing/src/app/blog/[slug]/page.tsx index 4a8dbdaae84..41c35df0db1 100644 --- a/apps/marketing/src/app/blog/[slug]/page.tsx +++ b/apps/marketing/src/app/blog/[slug]/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; import { MDXRemote } from "next-mdx-remote/rsc"; +import { ArticleJsonLd } from "@/components/JsonLd"; import { extractToc, getAllSlugs, getBlogPost } from "@/lib/blog"; import { mdxComponents } from "../components/mdx-components"; import { BlogPostLayout } from "./components/BlogPostLayout"; @@ -19,8 +20,18 @@ export default async function BlogPostPage({ params }: PageProps) { const toc = extractToc(post.content); + const url = `https://superset.sh/blog/${slug}`; + return (
+ @@ -42,13 +53,20 @@ export async function generateMetadata({ return {}; } + const url = `https://superset.sh/blog/${slug}`; + return { title: `${post.title} | Superset Blog`, description: post.description, + alternates: { + canonical: url, + }, openGraph: { title: post.title, description: post.description, type: "article", + url, + siteName: "Superset", publishedTime: post.date, authors: [post.author], ...(post.image && { images: [post.image] }), diff --git a/apps/marketing/src/app/feed.xml/route.ts b/apps/marketing/src/app/feed.xml/route.ts new file mode 100644 index 00000000000..b5863430c80 --- /dev/null +++ b/apps/marketing/src/app/feed.xml/route.ts @@ -0,0 +1,46 @@ +import { getBlogPosts } from "@/lib/blog"; + +export async function GET() { + const posts = getBlogPosts(); + const baseUrl = "https://superset.sh"; + + const escapeXml = (str: string) => + str + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + + const rss = ` + + + Superset Blog + ${baseUrl}/blog + News, updates, and insights from the Superset team about parallel coding agents and developer productivity. + en-us + ${new Date().toUTCString()} + + ${posts + .map( + (post) => ` + + ${escapeXml(post.title)} + ${baseUrl}/blog/${post.slug} + ${escapeXml(post.description || "")} + ${new Date(post.date).toUTCString()} + ${baseUrl}/blog/${post.slug} + ${escapeXml(post.author)} + `, + ) + .join("")} + +`; + + return new Response(rss, { + headers: { + "Content-Type": "application/xml", + "Cache-Control": "public, max-age=3600, s-maxage=3600", + }, + }); +} diff --git a/apps/marketing/src/app/layout.tsx b/apps/marketing/src/app/layout.tsx index 791d7f796a2..8ee7df70108 100644 --- a/apps/marketing/src/app/layout.tsx +++ b/apps/marketing/src/app/layout.tsx @@ -3,6 +3,11 @@ import { IBM_Plex_Mono, Inter } from "next/font/google"; import Script from "next/script"; import { CookieConsent } from "@/components/CookieConsent"; +import { + OrganizationJsonLd, + SoftwareApplicationJsonLd, + WebsiteJsonLd, +} from "@/components/JsonLd"; import { CTAButtons } from "./components/CTAButtons"; import { Footer } from "./components/Footer"; @@ -24,15 +29,70 @@ const inter = Inter({ }); export const metadata: Metadata = { - title: "Superset - Run 10+ parallel coding agents on your machine", + metadataBase: new URL("https://superset.sh"), + title: { + default: "Superset - Run 10+ parallel coding agents on your machine", + template: "%s | Superset", + }, description: "Run 10+ parallel coding agents on your machine. Spin up new coding tasks while waiting for your current agent to finish. Quickly switch between tasks as they need your attention.", + keywords: [ + "coding agents", + "parallel execution", + "developer tools", + "AI coding", + "git worktrees", + "code automation", + "Claude Code", + "Cursor", + "Codex", + ], + authors: [{ name: "Superset Team" }], + creator: "Superset", + openGraph: { + type: "website", + locale: "en_US", + url: "https://superset.sh", + siteName: "Superset", + title: "Superset - Run 10+ parallel coding agents on your machine", + description: + "Run 10+ parallel coding agents on your machine. Spin up new coding tasks while waiting for your current agent to finish.", + images: [ + { + url: "/og-image.png", + width: 1200, + height: 630, + alt: "Superset - The Terminal for Coding Agents", + }, + ], + }, + twitter: { + card: "summary_large_image", + title: "Superset - Run 10+ parallel coding agents on your machine", + description: + "Run 10+ parallel coding agents on your machine. Spin up new coding tasks while waiting for your current agent to finish.", + images: ["/og-image.png"], + creator: "@AviSupersetSH", + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + "max-video-preview": -1, + "max-image-preview": "large", + "max-snippet": -1, + }, + }, icons: { icon: [ { url: "/favicon.ico", sizes: "32x32" }, { url: "/favicon-192.png", sizes: "192x192", type: "image/png" }, ], + apple: [{ url: "/apple-touch-icon.png", sizes: "180x180" }], }, + manifest: "/manifest.json", }; export default function RootLayout({ @@ -51,6 +111,9 @@ export default function RootLayout({ src="https://tally.so/widgets/embed.js" strategy="afterInteractive" /> + + + diff --git a/apps/marketing/src/app/opengraph-image.tsx b/apps/marketing/src/app/opengraph-image.tsx new file mode 100644 index 00000000000..eb25070ca37 --- /dev/null +++ b/apps/marketing/src/app/opengraph-image.tsx @@ -0,0 +1,90 @@ +import { ImageResponse } from "next/og"; + +export const runtime = "edge"; +export const alt = "Superset - The Terminal for Coding Agents"; +export const size = { width: 1200, height: 630 }; +export const contentType = "image/png"; + +export default function Image() { + return new ImageResponse( +
+
+ Superset +
+
+ Run 10+ parallel coding agents on your machine +
+
+
+ Claude +
+
+ Codex +
+
+ Gemini +
+
+
, + { ...size }, + ); +} diff --git a/apps/marketing/src/app/robots.ts b/apps/marketing/src/app/robots.ts new file mode 100644 index 00000000000..3a462ca25d7 --- /dev/null +++ b/apps/marketing/src/app/robots.ts @@ -0,0 +1,14 @@ +import type { MetadataRoute } from "next"; + +export default function robots(): MetadataRoute.Robots { + return { + rules: [ + { + userAgent: "*", + allow: "/", + disallow: ["/api/", "/_next/"], + }, + ], + sitemap: "https://superset.sh/sitemap.xml", + }; +} diff --git a/apps/marketing/src/app/sitemap.ts b/apps/marketing/src/app/sitemap.ts new file mode 100644 index 00000000000..ece175c26eb --- /dev/null +++ b/apps/marketing/src/app/sitemap.ts @@ -0,0 +1,49 @@ +import type { MetadataRoute } from "next"; +import { getBlogPosts } from "@/lib/blog"; + +export default function sitemap(): MetadataRoute.Sitemap { + const baseUrl = "https://superset.sh"; + + const staticPages: MetadataRoute.Sitemap = [ + { + url: baseUrl, + lastModified: new Date(), + changeFrequency: "weekly", + priority: 1.0, + }, + { + url: `${baseUrl}/blog`, + lastModified: new Date(), + changeFrequency: "daily", + priority: 0.9, + }, + { + url: `${baseUrl}/privacy`, + lastModified: new Date("2025-01-15"), + changeFrequency: "yearly", + priority: 0.3, + }, + { + url: `${baseUrl}/terms`, + lastModified: new Date("2025-01-15"), + changeFrequency: "yearly", + priority: 0.3, + }, + { + url: `${baseUrl}/ports`, + lastModified: new Date(), + changeFrequency: "monthly", + priority: 0.5, + }, + ]; + + const posts = getBlogPosts(); + const blogPages: MetadataRoute.Sitemap = posts.map((post) => ({ + url: `${baseUrl}/blog/${post.slug}`, + lastModified: new Date(post.date), + changeFrequency: "monthly" as const, + priority: 0.8, + })); + + return [...staticPages, ...blogPages]; +} diff --git a/apps/marketing/src/components/JsonLd/JsonLd.tsx b/apps/marketing/src/components/JsonLd/JsonLd.tsx new file mode 100644 index 00000000000..5b5350be93e --- /dev/null +++ b/apps/marketing/src/components/JsonLd/JsonLd.tsx @@ -0,0 +1,148 @@ +interface OrganizationJsonLdProps { + url?: string; +} + +export function OrganizationJsonLd({ + url = "https://superset.sh", +}: OrganizationJsonLdProps) { + const schema = { + "@context": "https://schema.org", + "@type": "Organization", + name: "Superset", + url, + logo: `${url}/logo.png`, + description: "Run 10+ parallel coding agents on your machine", + sameAs: [ + "https://github.com/AviSupersetSH/superset", + "https://twitter.com/AviSupersetSH", + ], + }; + + return ( +