From 1d3130064837daa5d7a7547b7cf46b456ca8752a Mon Sep 17 00:00:00 2001 From: lbb00 Date: Wed, 4 Nov 2026 17:44:22 +0800 Subject: [PATCH 01/10] Improve docs site SEO metadata, sitemap, and README hook. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add description/Open Graph/Twitter/canonical/hreflang, force blocking metadata into the static HTML export, ship sitemap.xml/robots.txt/llms.txt and an og.png social card, and open the README with a clear EN+中文 hook. Do not push until reviewed. --- CHANGELOG.md | 2 + README.md | 6 ++- site-app/app/layout.tsx | 70 +++++++++++++++++++++++++++++++---- site-app/app/robots.ts | 15 ++++++++ site-app/app/sitemap.ts | 38 +++++++++++++++++++ site-app/assemble.js | 9 +++++ site-app/lib/docs-page.tsx | 50 ++++++++++++++++++++++++- site-app/lib/site-meta.ts | 28 ++++++++++++++ site-app/next.config.js | 3 ++ site-app/seo-static/llms.txt | 24 ++++++++++++ site-app/seo-static/og.png | Bin 0 -> 29873 bytes 11 files changed, 236 insertions(+), 9 deletions(-) create mode 100644 site-app/app/robots.ts create mode 100644 site-app/app/sitemap.ts create mode 100644 site-app/lib/site-meta.ts create mode 100644 site-app/seo-static/llms.txt create mode 100644 site-app/seo-static/og.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a6b960..4901e3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ Versions apply to the whole repository and are git tags (see [docs/release.md](d Kits are not released separately and carry no version of their own. ## [Unreleased] +- **Docs SEO:** site metadata now includes description, Open Graph, Twitter Card, canonical and en/zh alternates; static export forces blocking metadata into HTML (`htmlLimitedBots`); added `sitemap.xml`, `robots.txt`, `llms.txt`, and a 1200×630 `og.png` social card (copied from `site-app/seo-static/` during assemble). README opens with a clear value-prop hook (EN + 中文一句话). + - **Docs site:** page links no longer keep a trailing `.md` (that 404s on GitHub Pages). Assemble rewrites them to trailing-slash directory URLs relative to the page directory, fails the build if any `.md` hrefs remain, emits static files as root-absolute paths so the `/zh/` locale works, and publishes `LICENSE` / `NOTICE` as directories so `trailingSlash` can open them. - **All kits:** default matching is **`--scope all`** where the kit CLI supports it (opt in to **`--scope loop`** for the former in-loop-only match). Finding messages no longer imply non-loop sites are out of scope. Rule slugs containing `in-loop` were **renamed** to `*-hot` (including leftover `rs-map-new-in-loop` → `rs-map-new-hot`; see per-kit Renamed tables); old slugs are tombstoned under each kit `evidence/_removed/*.REMOVED.md` and must never be reused. See each kit `docs/rule-ids.md` Renamed table. jsperf also keeps adopter FP cuts + new hints from earlier commits on this branch. No severity escalations. diff --git a/README.md b/README.md index 0bb1198..3530979 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # XPerf -XPerf is a set of performance guides and heuristic linters, one kit per language under `packages/`. A lint hit means “look here,” not “this will be faster.” A rule stays a hint until paired fixtures and a measured before/after promote it. Rule IDs are semantic slugs (~169 active). +XPerf helps you find real performance smells across 19 languages — guides plus heuristic linters that stay honest: a hit means “look here,” not “this will be faster,” until paired fixtures and a measured before/after say otherwise. + +中文一句话:用可测量的启发式规则,在 19 种语言里定位性能味道;命中只表示「值得看」,不等于「改了就更快」。 + +Docs site: [lbb00.github.io/xperf](https://lbb00.github.io/xperf/). Rule IDs are semantic slugs (~169 active). ## Languages diff --git a/site-app/app/layout.tsx b/site-app/app/layout.tsx index dbc153d..fd2689a 100644 --- a/site-app/app/layout.tsx +++ b/site-app/app/layout.tsx @@ -1,20 +1,76 @@ +import type { Metadata } from 'next'; import type { ReactNode } from 'react'; +import { + OG_IMAGE_ALT, + OG_IMAGE_PATH, + SITE_DESCRIPTION, + SITE_NAME, + SITE_URL, +} from '@/lib/site-meta'; import './global.css'; -export const metadata = { +export const metadata: Metadata = { + metadataBase: new URL(`${SITE_URL}/`), title: { - default: 'XPerf', - template: '%s | XPerf', + default: SITE_NAME, + template: `%s | ${SITE_NAME}`, + }, + description: SITE_DESCRIPTION, + applicationName: SITE_NAME, + keywords: [ + 'XPerf', + 'performance', + 'linter', + 'static analysis', + 'heuristics', + 'Zig', + 'JavaScript', + 'Go', + 'Rust', + 'Python', + ], + authors: [{ name: 'XPerf contributors' }], + creator: 'XPerf', + openGraph: { + type: 'website', + locale: 'en_US', + url: SITE_URL, + siteName: SITE_NAME, + title: SITE_NAME, + description: SITE_DESCRIPTION, + images: [ + { + url: OG_IMAGE_PATH, + width: 1200, + height: 630, + alt: OG_IMAGE_ALT, + }, + ], + }, + twitter: { + card: 'summary_large_image', + title: SITE_NAME, + description: SITE_DESCRIPTION, + images: [OG_IMAGE_PATH], + }, + alternates: { + canonical: '/', + languages: { + en: '/', + 'zh-CN': '/zh/', + 'x-default': '/', + }, + }, + robots: { + index: true, + follow: true, }, - description: 'Measured performance guides and heuristic linters for 19 languages.', }; export default function Layout({ children }: { children: ReactNode }) { return ( - - {children} - + {children} ); } diff --git a/site-app/app/robots.ts b/site-app/app/robots.ts new file mode 100644 index 0000000..db07356 --- /dev/null +++ b/site-app/app/robots.ts @@ -0,0 +1,15 @@ +import type { MetadataRoute } from 'next'; +import { SITE_URL } from '@/lib/site-meta'; + +export const dynamic = 'force-static'; + +export default function robots(): MetadataRoute.Robots { + return { + rules: { + userAgent: '*', + allow: '/', + }, + sitemap: `${SITE_URL}/sitemap.xml`, + host: SITE_URL, + }; +} diff --git a/site-app/app/sitemap.ts b/site-app/app/sitemap.ts new file mode 100644 index 0000000..8c30dd8 --- /dev/null +++ b/site-app/app/sitemap.ts @@ -0,0 +1,38 @@ +import type { MetadataRoute } from 'next'; +import { SITE_URL } from '@/lib/site-meta'; +import { source } from '@/lib/source'; + +export const dynamic = 'force-static'; + +function withTrailingSlash(path: string): string { + if (path === '/') return '/'; + return path.endsWith('/') ? path : `${path}/`; +} + +export default function sitemap(): MetadataRoute.Sitemap { + const seen = new Set(); + const entries: MetadataRoute.Sitemap = []; + + for (const locale of ['en', 'zh'] as const) { + for (const page of source.getPages(locale)) { + const path = withTrailingSlash(page.url || '/'); + const url = `${SITE_URL}${path === '/' ? '/' : path}`; + if (seen.has(url)) continue; + seen.add(url); + + const depth = page.slugs?.length ?? 0; + let priority = 0.6; + if (depth === 0) priority = 1; + else if (page.slugs?.[0] === 'docs') priority = 0.8; + else if (page.slugs?.[0] === 'packages') priority = 0.7; + + entries.push({ + url, + changeFrequency: 'weekly', + priority, + }); + } + } + + return entries; +} diff --git a/site-app/assemble.js b/site-app/assemble.js index a2ffba9..2d58f77 100644 --- a/site-app/assemble.js +++ b/site-app/assemble.js @@ -640,6 +640,15 @@ function assemble() { } writeSidebar(); checkNav(copied); + const seoStatic = path.join(ROOT, 'site-app', 'seo-static'); + if (fs.existsSync(seoStatic)) { + for (const name of fs.readdirSync(seoStatic)) { + if (name.startsWith('.')) continue; + const src = path.join(seoStatic, name); + if (!fs.statSync(src).isFile()) continue; + fs.copyFileSync(src, path.join(PUBLIC, name)); + } + } console.log( `build-pages: assembled ${mdFiles.length} pages, ${files.length} files, rewrote ${rewritten} links`, ); diff --git a/site-app/lib/docs-page.tsx b/site-app/lib/docs-page.tsx index 453678f..c8fc478 100644 --- a/site-app/lib/docs-page.tsx +++ b/site-app/lib/docs-page.tsx @@ -4,6 +4,14 @@ import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; import { getMDXComponents } from '@/components/mdx'; import type { Locale } from '@/lib/i18n'; +import { + OG_IMAGE_ALT, + OG_IMAGE_PATH, + SITE_DESCRIPTION, + SITE_DESCRIPTION_ZH, + SITE_NAME, + pagePath, +} from '@/lib/site-meta'; import { source } from '@/lib/source'; function load(slugs: string[] | undefined, locale: Locale) { @@ -38,9 +46,49 @@ export function renderDocsPage(slugs: string[] | undefined, locale: Locale) { export function docsMetadata(slugs: string[] | undefined, locale: Locale): Metadata { const page = load(slugs, locale); const title = page.data.title === 'XPerf' ? { absolute: 'XPerf' } : page.data.title; + const fallback = locale === 'zh' ? SITE_DESCRIPTION_ZH : SITE_DESCRIPTION; + // Always set a string. Returning `description: undefined` from generateMetadata + // clears the root layout description in the static HTML export. + const description = page.data.description?.trim() || fallback; + const canonical = pagePath(slugs, locale); + const enPath = pagePath(slugs, 'en'); + const zhPath = pagePath(slugs, 'zh'); + const ogTitle = + typeof title === 'string' ? title : title.absolute === 'XPerf' ? SITE_NAME : String(page.data.title); + return { title, - description: page.data.description, + description, + alternates: { + canonical, + languages: { + en: enPath, + 'zh-CN': zhPath, + 'x-default': enPath, + }, + }, + openGraph: { + type: 'article', + locale: locale === 'zh' ? 'zh_CN' : 'en_US', + url: canonical, + siteName: SITE_NAME, + title: ogTitle, + description, + images: [ + { + url: OG_IMAGE_PATH, + width: 1200, + height: 630, + alt: OG_IMAGE_ALT, + }, + ], + }, + twitter: { + card: 'summary_large_image', + title: ogTitle, + description, + images: [OG_IMAGE_PATH], + }, }; } diff --git a/site-app/lib/site-meta.ts b/site-app/lib/site-meta.ts new file mode 100644 index 0000000..187c129 --- /dev/null +++ b/site-app/lib/site-meta.ts @@ -0,0 +1,28 @@ +/** Shared SEO / social metadata for the docs site (GitHub Pages under /xperf). */ + +export const SITE_URL = 'https://lbb00.github.io/xperf'; + +export const SITE_NAME = 'XPerf'; + +/** One-line value prop — keep honest: hints ≠ guaranteed speedups. */ +export const SITE_DESCRIPTION = + 'Performance guides and heuristic linters for 19 languages. A lint hit means look here, not this will be faster — rules stay hints until paired fixtures and measured before/after evidence.'; + +export const SITE_DESCRIPTION_ZH = + '19 种语言的性能指南与启发式检查器。命中表示「值得看」,不等于「改了就更快」;规则默认是 hint,直到有配对夹具和实测前后对比。'; + +export const OG_IMAGE_PATH = '/og.png'; + +export const OG_IMAGE_ALT = 'XPerf — measured performance guides and heuristic linters'; + +export function pagePath(slugs: string[] | undefined, locale: 'en' | 'zh'): string { + const parts = slugs?.filter(Boolean) ?? []; + const tail = parts.length ? `${parts.join('/')}/` : ''; + if (locale === 'zh') return `/zh/${tail}`; + return `/${tail}`; +} + +export function absoluteUrl(path: string): string { + const normalized = path.startsWith('/') ? path : `/${path}`; + return `${SITE_URL}${normalized}`; +} diff --git a/site-app/next.config.js b/site-app/next.config.js index 4c935ce..4e18904 100644 --- a/site-app/next.config.js +++ b/site-app/next.config.js @@ -9,6 +9,9 @@ const config = { reactStrictMode: true, agentRules: false, trailingSlash: true, + // Static GitHub Pages has no per-request UA. Force blocking metadata into the + // exported HTML so description / Open Graph / Twitter tags are present for crawlers. + htmlLimitedBots: /.*/, ...(basePath ? { basePath, assetPrefix: basePath } : {}), ...(staticExport ? { output: 'export', images: { unoptimized: true } } : {}), }; diff --git a/site-app/seo-static/llms.txt b/site-app/seo-static/llms.txt new file mode 100644 index 0000000..8534edf --- /dev/null +++ b/site-app/seo-static/llms.txt @@ -0,0 +1,24 @@ +# XPerf + +> Performance guides and heuristic linters for 19 languages. A lint hit means "look here," not "this will be faster." Rules stay hints until paired fixtures and measured before/after evidence promote them. + +XPerf is an umbrella monorepo: each language kit under packages/ ships its own guide, examples, fixtures, and a same-language (or language-appropriate) heuristic linter. Severity escalates to default error only with evidence — never invent bench numbers. + +Site: https://lbb00.github.io/xperf/ +Repository: https://github.com/lbb00/xperf +License: MIT + +## Key docs + +- [Overview](https://lbb00.github.io/xperf/docs/overview/): architecture and kit status +- [Quick start](https://lbb00.github.io/xperf/docs/quickstart/): run a linter +- [Playbook](https://lbb00.github.io/xperf/docs/playbook/): inventory → evidence → corpus → reverse-opt +- [Smell catalog](https://lbb00.github.io/xperf/docs/smell-catalog/): cross-language smell index +- [Kits](https://lbb00.github.io/xperf/docs/kits/): versions and defaults +- [Languages](https://lbb00.github.io/xperf/packages/): per-kit packages (zperf, jsperf, goperf, …) +- [Chinese locale](https://lbb00.github.io/xperf/zh/): 中文界面(部分页面仍为英文原文) + +## Optional + +- [AGENT.md](https://lbb00.github.io/xperf/AGENT/): contributor/agent gates (G0–G7), not product marketing +- [Changelog](https://lbb00.github.io/xperf/CHANGELOG/) diff --git a/site-app/seo-static/og.png b/site-app/seo-static/og.png new file mode 100644 index 0000000000000000000000000000000000000000..32c0212876c53adaa7e2d494a4deb629983021c5 GIT binary patch literal 29873 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 j$K#y;FSqsY;D)1 Date: Wed, 4 Nov 2026 18:45:56 +0800 Subject: [PATCH 02/10] Drop Chinese from README; add GEO JSON-LD and AI crawler rules. Keep README English-only. Expand llms.txt for generative engines, allow common AI crawlers in robots.txt, and emit WebSite/SoftwareApplication JSON-LD on every page. --- CHANGELOG.md | 2 +- README.md | 2 -- site-app/app/layout.tsx | 10 +++++++++- site-app/app/robots.ts | 28 ++++++++++++++++++++++++---- site-app/lib/site-meta.ts | 35 ++++++++++++++++++++++++++++++++++- site-app/seo-static/llms.txt | 8 +++++++- 6 files changed, 75 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4901e3c..305ea3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ Versions apply to the whole repository and are git tags (see [docs/release.md](d Kits are not released separately and carry no version of their own. ## [Unreleased] -- **Docs SEO:** site metadata now includes description, Open Graph, Twitter Card, canonical and en/zh alternates; static export forces blocking metadata into HTML (`htmlLimitedBots`); added `sitemap.xml`, `robots.txt`, `llms.txt`, and a 1200×630 `og.png` social card (copied from `site-app/seo-static/` during assemble). README opens with a clear value-prop hook (EN + 中文一句话). +- **Docs SEO / GEO:** site metadata now includes description, Open Graph, Twitter Card, canonical and en/zh alternates; static export forces blocking metadata into HTML (`htmlLimitedBots`); added `sitemap.xml`, `robots.txt`, `llms.txt`, and a 1200×630 `og.png` social card (copied from `site-app/seo-static/` during assemble). README opens with a clear English value-prop hook. GEO: `llms.txt`, JSON-LD SoftwareApplication/WebSite, and explicit AI-crawler allow rules in `robots.txt`. - **Docs site:** page links no longer keep a trailing `.md` (that 404s on GitHub Pages). Assemble rewrites them to trailing-slash directory URLs relative to the page directory, fails the build if any `.md` hrefs remain, emits static files as root-absolute paths so the `/zh/` locale works, and publishes `LICENSE` / `NOTICE` as directories so `trailingSlash` can open them. diff --git a/README.md b/README.md index 3530979..a5f7693 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ XPerf helps you find real performance smells across 19 languages — guides plus heuristic linters that stay honest: a hit means “look here,” not “this will be faster,” until paired fixtures and a measured before/after say otherwise. -中文一句话:用可测量的启发式规则,在 19 种语言里定位性能味道;命中只表示「值得看」,不等于「改了就更快」。 - Docs site: [lbb00.github.io/xperf](https://lbb00.github.io/xperf/). Rule IDs are semantic slugs (~169 active). ## Languages diff --git a/site-app/app/layout.tsx b/site-app/app/layout.tsx index fd2689a..7b190e3 100644 --- a/site-app/app/layout.tsx +++ b/site-app/app/layout.tsx @@ -6,6 +6,7 @@ import { SITE_DESCRIPTION, SITE_NAME, SITE_URL, + siteJsonLd, } from '@/lib/site-meta'; import './global.css'; @@ -68,9 +69,16 @@ export const metadata: Metadata = { }; export default function Layout({ children }: { children: ReactNode }) { + const jsonLd = siteJsonLd(); return ( - {children} + +