fix(seo): hreflang open graph robots and redirect chains #33
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/seo/hreflang-og-robots-chains"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Что сделано
Реализована issue #30 (зависела от PR#32, который уже в main). 6 пунктов спеки:
In-HTML hreflang — во все
generateMetadata(home, apps, contacts, subscriptions,[slug]) добавленalternates: { languages: { ru, en, "x-default" } }. Next.js эмитит<link rel="alternate" hreflang="..." href="...">в<head>. Для[slug]— hreflang только если repo найден (возвращает{}без hreflang для 404).Legacy redirect цепочки — убраны non-locale legacy redirects из
next.config.ts(/youtube,/take_break,/support), перенесены в middleware с 308 и locale изgetLocale(request): один hopGET /youtube→ 308 →/ru/yt-video-downloader(вместо двух-hop цепочки 308→307). Locale-prefixed legacy redirects (/ru/youtube,/en/youtubeи т.д.) оставлены вnext.config.ts— matcher middleware исключает/ru/*и/en/*(CDN serve static, по дизайну PR#32), middleware не может их ловить в production. При прямом вызове middleware ловит и locale-prefixed legacy (тест покрывает). Координация с PR#32:LEGACY_REDIRECTSдобавлен обратно как живой код (PR#32 удалил dead-code версию).og:image — добавлен
images: [{ url: "/og-image.png", width: 1024, height: 1024 }]вopenGraphдля apps/contacts/subscriptions (child page override'ил родительский layout, images терялись).robots.txt disallow —
rules: { userAgent: "*", allow: "/", disallow: ["/api/"] }.__404__route удалён в PR#32 → disallow только/api/.JSON-LD @id коллизия —
apps/page.tsx:@typeWebSite→CollectionPage,@id${baseUrl}/${loc}→${baseUrl}/${loc}/apps, добавленisPartOf: { "@type": "WebSite", "@id": ${baseUrl}/${loc} }. Удалён дублирующийPersonJSON-LD (остался только на home).trailingSlash —
trailingSlash: falseявно вnext.config.ts.Почему
Сайт терял SEO-сигналы: Google не понимал связь ru/en (нет in-HTML hreflang), redirect-цепочки dilute link equity (2-hop вместо 1), плохие social cards на apps/contacts/subscriptions (нет og:image), crawl budget waste (robots.txt не disallow
/api/), JSON-LD @id коллизия (home и apps обаWebSite @id=/ru→ Google merge/confuse entities).Watch out
— Locale-prefixed legacy redirects (
/ru/youtubeи т.д.) остались вnext.config.ts, НЕ в middleware — matcher исключает/ru/*//en/*(PR#32 дизайн, CDN serve static). Non-locale (/youtube) — в middleware (один hop с locale). Это split design, не oversight.—
CollectionPageJSON-LD используетd.apps.title(repos.length)дляname(локализованный заголовок с count), а не hardcoded "slaid098.dev" (как было в WebSite).Pending
—
Closes #30
Code Review Summary
PR реализует все 6 пунктов спеки issue #30 (hreflang, redirect chains, og:image, robots disallow, JSON-LD @id collision, trailingSlash). CI green, тесты middleware расширены (+10 кейсов для legacy redirects), cross-file impact чистый (writer+reader в одном PR).
Positives
generateMetadata(home, apps, contacts, subscriptions,[slug]) добавляютalternates.languagesс ru/en/x-default. Для[slug]hreflang только если repo найден (if (!repo) return {}— корректно, нет hreflang на 404). x-default = ru (default locale) — консистентно./youtube,/take_break,/support) перенесены изnext.config.tsв middleware с 308 + locale изgetLocale(request)(cookie → Accept-Language → default). Раньше было 2-hop (308 next.config → 307 middleware), теперь 1-hop (308 middleware). Locale-prefixed legacy (/ru/youtube) оставлены вnext.config.ts— matcher middleware исключает/ru/*/en/*(CDN serve static, дизайн PR#32). Split design задокументирован в PR body "Watch out" — не oversight.apps/page.tsx:WebSite @id=${baseUrl}/${loc}→CollectionPage @id=${baseUrl}/${loc}/apps+isPartOf: { WebSite, @id=${baseUrl}/${loc} }. Коллизия с home устранена. ДублирующийPersonJSON-LD удалён из apps (остался только на home). Все @id теперь уникальны per page: home=/${loc}, apps=/${loc}/apps, contacts=/${loc}/contacts, subscriptions=/${loc}/subscriptions, [slug]=/${loc}/${slug}.images: [{ url: "/og-image.png", width: 1024, height: 1024 }]в apps/contacts/subscriptions. Файлsrc/app/og-image.pngсуществует (tracked, PR#20), Next.js file-metadata convention обслуживает/og-image.png. og:url = canonical URL — консистентно во всех страницах.disallow: ["/api/"](раньше не было).__404__route удалён в PR#32 → disallow только/api/./ru/apps. Все кейсы покрывают split design.fix(seo): ...соответствует конвенции, body содержит Что сделано/Почему/Watch out/Pending заполнены осмысленно,Closes #30присутствует.Suggestions (info, not blocking)
const locale = first as Locale;— избыточный cast.isLocale(first)это type guard (str is Locale), поэтому внутриif (first !== undefined && isLocale(first))TypeScript уже сужаетfirstдоLocale. Cast можно убрать:const locale = first;. Не влияет на runtime, чисто type-level.alternates: { canonical, languages: { ru, en, "x-default" } }повторяется в 5 файлах с разными path-значениями. Можно вынести в хелперmakeAlternates(baseUrl, loc, path), но т.к. значения различаются (path per page) — абстракция добавит мало пользы. Оставлено на усмотрение автора.Verdict: APPROVE