From d0ea43caaa5fe866768ce7264485d000dd473eee Mon Sep 17 00:00:00 2001 From: Serhii Zhabskyi Date: Tue, 31 Mar 2026 12:26:14 +0200 Subject: [PATCH] fix(website): unify canonical origin, trailing slashes, and robots sitemap Made-with: Cursor --- .github/workflows/deploy-website.yml | 6 +++++ website/astro.config.mjs | 13 ++++++++--- website/integrations/seo-robots.mjs | 21 +++++++++++++++++ website/public/robots.txt | 4 ---- website/site-url.mjs | 23 +++++++++++++++++++ .../SoftwareApplicationJsonLd.astro | 23 +++++++++++++++++++ website/src/content/docs/index.mdx | 23 ++----------------- 7 files changed, 85 insertions(+), 28 deletions(-) create mode 100644 website/integrations/seo-robots.mjs delete mode 100644 website/public/robots.txt create mode 100644 website/site-url.mjs create mode 100644 website/src/components/SoftwareApplicationJsonLd.astro diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml index 79ceb5c2..b333ef03 100644 --- a/.github/workflows/deploy-website.yml +++ b/.github/workflows/deploy-website.yml @@ -6,6 +6,10 @@ # requires a token other than GITHUB_TOKEN to enable Pages via API. # # Live site: https://samplexbro.github.io/agentsmesh/ (see website/astro.config.mjs `site` + `base`). +# +# SEO: set repository variable DEPLOY_SITE_URL to the exact origin you want indexed +# (HTTPS, no trailing slash), e.g. https://samplexbro.github.io or your custom domain. +# Configure DNS or CDN to 301 the non-canonical hostname (www ↔ apex) to that URL. name: Deploy Website on: @@ -53,6 +57,8 @@ jobs: - name: Build website working-directory: website + env: + DEPLOY_SITE_URL: ${{ vars.DEPLOY_SITE_URL }} run: pnpm run build - name: Upload Pages artifact diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 8503d8ea..a865c3ca 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -1,9 +1,15 @@ // @ts-check import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; +import seoRobotsIntegration from './integrations/seo-robots.mjs'; +import { absoluteFromBase, getSiteOrigin } from './site-url.mjs'; + +const site = getSiteOrigin(); +const ogImage = absoluteFromBase('/og-image.png'); export default defineConfig({ - site: 'https://samplexbro.github.io', + site, + trailingSlash: 'always', base: '/agentsmesh', integrations: [ starlight({ @@ -30,7 +36,7 @@ export default defineConfig({ head: [ { tag: 'meta', - attrs: { property: 'og:image', content: 'https://samplexbro.github.io/agentsmesh/og-image.png' }, + attrs: { property: 'og:image', content: ogImage }, }, { tag: 'meta', @@ -38,7 +44,7 @@ export default defineConfig({ }, { tag: 'meta', - attrs: { name: 'twitter:image', content: 'https://samplexbro.github.io/agentsmesh/og-image.png' }, + attrs: { name: 'twitter:image', content: ogImage }, }, { tag: 'meta', @@ -117,5 +123,6 @@ export default defineConfig({ }, ], }), + seoRobotsIntegration(() => getSiteOrigin()), ], }); diff --git a/website/integrations/seo-robots.mjs b/website/integrations/seo-robots.mjs new file mode 100644 index 00000000..33d7cf22 --- /dev/null +++ b/website/integrations/seo-robots.mjs @@ -0,0 +1,21 @@ +import { writeFileSync } from 'node:fs'; + +/** + * @param {() => string} getOrigin Host-only HTTPS URL, no trailing slash + */ +export default function seoRobotsIntegration(getOrigin) { + return { + name: 'seo-robots', + hooks: { + 'astro:build:done': ({ dir }) => { + const origin = getOrigin().replace(/\/$/, ''); + const body = `User-agent: * +Allow: / + +Sitemap: ${origin}/agentsmesh/sitemap-index.xml +`; + writeFileSync(new URL('robots.txt', dir), body, 'utf8'); + }, + }, + }; +} diff --git a/website/public/robots.txt b/website/public/robots.txt deleted file mode 100644 index 25dab986..00000000 --- a/website/public/robots.txt +++ /dev/null @@ -1,4 +0,0 @@ -User-agent: * -Allow: / - -Sitemap: https://samplexbro.github.io/agentsmesh/sitemap-index.xml diff --git a/website/site-url.mjs b/website/site-url.mjs new file mode 100644 index 00000000..15cf0bbd --- /dev/null +++ b/website/site-url.mjs @@ -0,0 +1,23 @@ +/** + * Single source of truth for the docs site origin. Set DEPLOY_SITE_URL in CI + * (GitHub repository variable) to your indexed hostname — e.g. apex HTTPS URL + * with no trailing slash. Configure DNS/CDN to 301 the non-canonical host (www vs apex). + */ + +/** @returns {string} e.g. https://samplexbro.github.io */ +export function getSiteOrigin() { + const raw = + process.env.DEPLOY_SITE_URL?.trim() || + process.env.SITE_URL?.trim() || + 'https://samplexbro.github.io'; + return raw.replace(/\/$/, ''); +} + +/** @param {string} pathWithLeadingSlash path after base, e.g. /og-image.png */ +export function absoluteFromBase(pathWithLeadingSlash) { + const origin = getSiteOrigin(); + const suffix = pathWithLeadingSlash.startsWith('/') + ? pathWithLeadingSlash + : `/${pathWithLeadingSlash}`; + return `${origin}/agentsmesh${suffix}`; +} diff --git a/website/src/components/SoftwareApplicationJsonLd.astro b/website/src/components/SoftwareApplicationJsonLd.astro new file mode 100644 index 00000000..2509b67d --- /dev/null +++ b/website/src/components/SoftwareApplicationJsonLd.astro @@ -0,0 +1,23 @@ +--- +const origin = Astro.site?.origin ?? 'https://samplexbro.github.io'; +const base = import.meta.env.BASE_URL; +const docsRoot = `${origin.replace(/\/$/, '')}${base}`.replace(/\/?$/, '/'); + +const jsonLd = { + '@context': 'https://schema.org', + '@type': 'SoftwareApplication', + name: 'AgentsMesh', + description: + 'Open-source CLI that syncs AI coding assistant configuration across every major tool — Claude Code, Cursor, Copilot, Gemini CLI, Windsurf, and more. One canonical .agentsmesh/ directory with bidirectional import, generate, community skill registry, and CI drift detection.', + applicationCategory: 'DeveloperApplication', + operatingSystem: 'Node.js 20+', + offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' }, + license: 'https://opensource.org/licenses/MIT', + url: docsRoot, + downloadUrl: 'https://www.npmjs.com/package/agentsmesh', + codeRepository: 'https://github.com/sampleXbro/agentsmesh', + author: { '@type': 'Person', name: 'sampleXbro' }, +}; +--- + +