mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix(docs): keep the translated locale trees out of the search index (#662)
Search Console flagged four reasons on 2026-07-28: soft 404, both duplicate-canonical variants, and noindex. All four sat on docs.snapotter.com; the landing site was clean. The cause was boilerplate dominance rather than a broken tag. /tools/video/crop-video carries 1.3 KB of unique body against 3.5 KB of identical chrome (nav, sidebar, 21-language switcher), so unrelated tool docs measured 52-60% full-page similarity. Across 20 locales that was 3,640 of 3,822 submitted URLs. Google read the lot as one duplicate cluster and began electing arbitrary representatives: /changelog became the canonical for /tools/image/favicon, and /uk/guide/getting-started for /nl/tools/image/resize. English tool docs indexed 2 of 10 and localized 4 of 10, while the landing page for those same five tools indexed 5 of 5. Translated pages now emit `noindex, follow` with a self-canonical, and sitemap.transformItems drops them from the sitemap. Self-canonical rather than pointing at English, since noindex paired with a cross-canonical sends two conflicting instructions. hreflang is removed outright: the annotation only means something between pages that can all be indexed. Readers see no change. The language switcher and every in-page link behave exactly as before. Verified against a real build: sitemap 3,822 to 182 URLs with zero translated entries, all 3,640 translated files carrying the noindex and no English file doing so, docs e2e 100 passed.
This commit is contained in:
@@ -38,14 +38,21 @@ test.describe("docs i18n (English + committed German locale)", () => {
|
||||
await expect(target).toBeInViewport();
|
||||
});
|
||||
|
||||
test("hreflang alternates are reciprocal and include x-default", async ({ page }) => {
|
||||
test("English pages are indexable and self-canonical", async ({ page }) => {
|
||||
await page.goto("/guide/getting-started");
|
||||
const en = await page.locator('link[hreflang="en"]').getAttribute("href");
|
||||
const de = await page.locator('link[hreflang="de"]').getAttribute("href");
|
||||
const xd = await page.locator('link[hreflang="x-default"]').getAttribute("href");
|
||||
expect(en).toContain("/guide/getting-started");
|
||||
expect(de).toContain("/de/guide/getting-started");
|
||||
expect(xd).toBe(en);
|
||||
const canonical = await page.locator('link[rel="canonical"]').getAttribute("href");
|
||||
expect(canonical).toBe("https://docs.snapotter.com/guide/getting-started");
|
||||
await expect(page.locator('meta[name="robots"]')).toHaveCount(0);
|
||||
});
|
||||
|
||||
// Only English is indexable, so there is no hreflang cluster left to annotate.
|
||||
// See the transformHead comment in apps/docs/.vitepress/config.mts.
|
||||
test("translated pages are noindex, self-canonical, and carry no hreflang", async ({ page }) => {
|
||||
await page.goto("/de/guide/getting-started");
|
||||
const canonical = await page.locator('link[rel="canonical"]').getAttribute("href");
|
||||
expect(canonical).toBe("https://docs.snapotter.com/de/guide/getting-started");
|
||||
await expect(page.locator('meta[name="robots"]')).toHaveAttribute("content", "noindex, follow");
|
||||
await expect(page.locator("link[hreflang]")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("pagefind returns results within the German locale", async ({ page }) => {
|
||||
|
||||
@@ -267,7 +267,7 @@ test("docs Playwright collection includes explicit Axe coverage", () => {
|
||||
for (const pageName of ["homepage", "getting started", "configuration", "REST API"]) {
|
||||
expect(output).toContain(`${pageName} has no Axe violations`);
|
||||
}
|
||||
expect(output).toContain("Total: 99 tests in 7 files");
|
||||
expect(output).toContain("Total: 100 tests in 7 files");
|
||||
});
|
||||
|
||||
test("Turbo passes surface harness overrides through task boundaries", () => {
|
||||
|
||||
Reference in New Issue
Block a user