Communitygithub.com

VadimOnix/xray-decky

The Astro marketing site in site/ — URL/SEO contract (build.format preserve, .html canonicals, sitemap serialize), 5-locale i18n structure, GitHub Pages deploy workflow, and how to verify Astro major-version bumps. Use when touching anything under site/, reviewing dependabot bumps of astro or its integrations, debugging Pages deploys, or auditing SEO output (canonical, hreflang, sitemap).

What is xray-decky?

xray-decky is a Claude Code agent skill that the Astro marketing site in site/ — URL/SEO contract (build.format preserve, .html canonicals, sitemap serialize), 5-locale i18n structure, GitHub Pages deploy workflow, and how to verify Astro major-version bumps. Use when touching anything under site/, reviewing dependabot bumps of astro or its integrations, debugging Pages deploys, or auditing SEO output (canonical, hreflang, sitemap).

Works with~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/VadimOnix/xray-decky/tree/HEAD/.claude/skills/site-astro

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

Astro site (site/)

Standalone pnpm project (own lockfile, not part of the plugin build). Astro + @astrojs/sitemap + @astrojs/check. Deployed to GitHub Pages at https://vadimonix.github.io/xray-decky/.

cd site
pnpm install
pnpm run check    # astro check — type-checks locale dictionaries; CI gate
pnpm run build    # → site/dist
pnpm run dev

URL & SEO contract (the part that silently breaks)

Everything hangs together only if these stay consistent:

  • build.format: 'preserve' → flat pages build to changelog.html, locale roots to ru/index.html (directories).
  • Canonical forms: flat pages end in .html; locale roots use trailing slash (…/ru/); site root is …/xray-decky/. Generated by src/lib/urls.ts (absoluteUrl, withBase, localeHref).
  • Sitemap must string-match canonicals. The serialize hook in astro.config.mjs rewrites both <loc> and the auto-generated hreflang <xhtml:link> alternates: locale-root segments (LOCALE_DIRS) get /, everything else extensionless gets .html. History: before 2026-08 it only rewrote <loc> and blanket-appended .html, sending crawlers to nonexistent ru.html/zh.html/fa.html/es.html.
  • Sitemap hreflang set must equal the page's hreflang set. Base.astro emits 5 locales + x-default; the integration emits only the 5, so serialize appends x-default (= the en URL). Google reads annotations from both delivery methods and treats differing sets as conflicting, not as a subset. seo-check --site compares them per URL.
  • robots.txt is inert on this deploy. It ships to /xray-decky/robots.txt, but crawlers read only https://vadimonix.github.io/robots.txt, which 404s (no user-site repo) — a 404 means "crawl everything", so nothing is blocked, but the Sitemap: line is never seen. Sitemap discovery rests entirely on the Search Console submission. Real fix, outside this repo: a VadimOnix/vadimonix.github.io repo serving a host-root robots.txt, or a custom domain.
  • GitHub Pages is lenient (extensionless file → 200, dir without slash → 301) — never rely on that; match canonicals exactly.
  • compressHTML: false is deliberate: keeps <head> (canonical, hreflang, OG, JSON-LD) greppable for SEO audits; gzip recovers the size.

Adding a locale = one place: src/i18n/locales.ts (LOCALES, localePrefixes, hreflangCodes) + dictionary file typed by src/i18n/types.ts + LOCALE_DIRS in astro.config.mjs + locales map in the sitemap integration config. astro check fails on missing dictionary keys — that's why CI runs it before build.

Deploy

.github/workflows/pages.yml: triggers on push to master touching site/** (or the workflow itself). Node 22, pnpm 9, --frozen-lockfile, check → build → upload site/dist → deploy. No PR builds — PR CI does not build the site, so a green PR says nothing about the site.

Verifying an Astro major bump (empirical, not changelog-reading)

Dependabot bumps only astro — peer integrations (@astrojs/sitemap, @astrojs/check) lag; verify they still work rather than assuming.

  1. Build the bumped version locally: pnpm install --frozen-lockfile && pnpm run check && pnpm run build.
  2. Diff dist/ layout against the previous build (same 11+ routes, locale dirs intact, nothing flattened).
  3. grep -o '<loc>[^<]*</loc>' dist/sitemap-0.xml — every URL must match a real file's canonical form.
  4. Spot-check <link rel="canonical"> and hreflang in dist/index.html and one locale page.
  5. After merge, watch the Pages run and probe live URLs (curl -s -o /dev/null -w "%{http_code}").

Known thresholds: Astro 6 requires Node ≥22 (pages.yml already on 22) and dropped CJS configs (ours is .mjs); i18n.routing.redirectToDefaultLocale default changed (irrelevant here — prefixDefaultLocale: false). Astro 7 moved to Vite 8/Rolldown + Rust compiler — mostly affects integrations/custom Vite config, which this site doesn't have. Upgrade guides: https://docs.astro.build/en/guides/upgrade-to/v6/, https://docs.astro.build/en/guides/upgrade-to/v7/.

Misc

  • optimize-images.sh — image pipeline for public/; run after adding heavy assets.
  • Search Console / Yandex verification tags live in the layout head — don't strip them.
  • Wording/positioning rules for the site copy (and every other user-facing surface — READMEs, plugin.json, repo metadata) live in the seo-surfaces skill + AGENTS.md "SEO surfaces". CI: node scripts/seo-check.mjs --site runs against dist/ in the Pages workflow.

Related Skills