fix(seo): hreflang, open graph, robots and redirect chains #30
Labels
No labels
bug
performance
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
slaid098/slaid098-dev#30
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Контекст
Сайт slaid098.dev теряет SEO-сигналы: нет in-HTML hreflang тегов (Google не понимает связь ru/en), redirect-цепочки dilute link equity (
/youtube→ 308 →/yt-video-downloader→ 307 →/ru/yt-video-downloader), нет og:image на apps/contacts/subscriptions (плохие social cards), robots.txt не disallow/api/(crawl budget waste), JSON-LD @id коллизия (home и apps обаWebSite @id=/ru).Текущее состояние (подтверждено аудитом актуального кода):
Нет hreflang в HTML —
grep "alternates.*languages"поsrc/→ толькоsrc/app/sitemap.ts:53используетalternates.languages. Ни одна page.tsx не определяетalternates: { languages: {...} }вgenerateMetadata. Next.js поддерживает это для эмиссии<link rel="alternate" hreflang="ru" ...>в<head>. Sitemap hreflang — вторичный сигнал; primary — in-HTML теги.Legacy redirect цепочка —
next.config.ts:7-10:/youtube→permanent: true(308) →/yt-video-downloader(без locale). Затем/yt-video-downloaderидёт в middleware → 307 →/ru/yt-video-downloader. Итого 2-hop chain (308 → 307).next.config.tsredirects не имеют доступа к Accept-Language. MiddlewareLEGACY_REDIRECTS(src/middleware.ts:5-8) мог бы редиректить сразу с locale, но это dead code (next.config перехватывает первым). Live:curl -sI https://slaid098.dev/youtube→ 308 →/yt-video-downloader.og:url/og:image missing —
src/app/[locale]/apps/page.tsx:21-27,contacts/page.tsx:20-26,subscriptions/page.tsx:19-25,%5F%5F404%5F%5F/page.tsx:16-19—openGraphбезimages. Layout ([locale]/layout.tsx:44) имеетimages: [{ url: "/og-image.png", ... }], но child page'sopenGraphoverride'ит родительский → images теряются. Home (page.tsx:24) и[slug]([slug]/page.tsx:49) имеют images. apps/contacts/subscriptions имеютurl, но НЕ имеютimages.robots.txt no disallow —
src/app/robots.ts:7—rules: { userAgent: "*", allow: "/" }. Нетdisallowдля/api/,/_next/. Crawl budget waste.JSON-LD @id коллизия —
src/app/[locale]/page.tsx:40(WebSite@id: ${baseUrl}/${loc}) иsrc/app/[locale]/apps/page.tsx:54(WebSite@id: ${baseUrl}/${loc}) — одинаковый@id. Schema.org@idдолжен быть уникальным. Google может merge/confuse entities. Дополнительно:apps/page.tsx:60-66содержит дублирующийPersonJSON-LD (уже есть на home,page.tsx:46-52).trailingSlash не задан —
next.config.ts— нетtrailingSlash. Defaultfalse, но route regexes принимают optional trailing slash ((?:/)?$) →/ru/apps/и/ru/apps— потенциально дубли.Задача
In-HTML hreflang — во ВСЕ
generateMetadataфункций добавьalternates: { languages: { ru: \${baseUrl}/ru${path}`, en: `${baseUrl}/en${path}`, "x-default": `${baseUrl}/ru${path}` } }гдеpath— путь без locale префикса. Файлы:src/app/[locale]/page.tsx(path=""),src/app/[locale]/apps/page.tsx(path="/apps"),src/app/[locale]/contacts/page.tsx(path="/contacts"),src/app/[locale]/subscriptions/page.tsx(path="/subscriptions"),src/app/[locale]/[slug]/page.tsx(path=/${repo.slug}, только если repo найден). Для[slug]— hreflang на counterpart locale только если repo существует (он существует в обоих locale, same slug). Next.js сгенерируетв<head>`.Legacy redirect цепочки — убрать legacy redirects из
next.config.ts(строки 7-50 для/youtube,/take_break,/supportи их locale-вариантов). Перенести в middleware (еслиLEGACY_REDIRECTSещё не удалён в issue #29 — используй его; если удалён — добавь обратно живую версию). Middleware имеетgetLocale(request)→ может редиректить сразу/{locale}/yt-video-downloaderс 308. ВАЖНО: coordination с issue #29 — если #29 удаляетLEGACY_REDIRECTS, эта issue должна добавить их обратно как живой код (с 308). Обнови тесты.og:image — добавь в
openGraphдля apps/contacts/subscriptions:images: [{ url: "/og-image.png", width: 1024, height: 1024 }](layout's image, если Next.js merge не работает — добавь явно). Файлы:apps/page.tsx:21,contacts/page.tsx:20,subscriptions/page.tsx:19. og:url уже присутствует во всех трёх.robots.txt disallow —
src/app/robots.ts—rules: { userAgent: "*", allow: "/", disallow: ["/api/", "/*/__404__"] }. Если/__404__route удалён в issue #29 — disallow только/api/./_next/тоже можно disallow (но Next.js сам управляет, опционально).JSON-LD @id коллизия —
src/app/[locale]/apps/page.tsx:51-59— измени JSON-LD@typeсWebSiteнаCollectionPage,@idна${baseUrl}/${loc}/apps, добавьisPartOf: { "@type": "WebSite", "@id": ${baseUrl}/${loc} }(ссылка на home WebSite). Убери дублирующийPersonJSON-LD из apps page (строки 60-66) — оставить только на home.trailingSlash —
next.config.ts— добавьtrailingSlash: false(явное). Проверь:/ru/apps/→ 308 →/ru/apps(или 404 — уточни поведение Next.js сtrailingSlash: false).Контракты
<head>каждой валидной страницы содержит<link rel="alternate" hreflang="ru" href="https://slaid098.dev/ru{path}">иhreflang="en"иhreflang="x-default"GET /youtube→ 308 →/ru/yt-video-downloader(один hop, не два)GET /take_break→ 308 →/ru/take-breakGET /support→ 308 →/ru/contactsGET /ru/youtube→ 308 →/ru/yt-video-downloader(один hop)/ru/apps=https://slaid098.dev/ru/apps/ru/apps=https://slaid098.dev/og-image.pngDisallow: /api//ru/apps—@type: CollectionPage,@id: https://slaid098.dev/ru/apps/ru/apps/→ redirect на/ru/apps(trailingSlash: false)Инварианты
x-defaulthreflang =ru(default locale)/og-image.png(для apps/contacts/subscriptions) или repo cover (для [slug])Allow: /сохраняется, добавляетсяDisallow: /api/@idуникальны per pageГраничные случаи
generateMetadataвозвращает{}без hreflang)next.config.tsredirects для locale-префиксированных вариантов (/ru/youtube) — убедиться что middleware ловит их тоже (или оставь в next.config если middleware не может)[slug]— уже использует repo cover ([slug]/page.tsx:49), НЕ override'ить на/og-image.pngx-defaultуказывает на URL для пользователей без match по localeВлияние на связанные компоненты
src/app/[locale]/page.tsx— hreflang в metadatasrc/app/[locale]/apps/page.tsx— hreflang + og:image + JSON-LD fix + удаление Personsrc/app/[locale]/contacts/page.tsx— hreflang + og:imagesrc/app/[locale]/subscriptions/page.tsx— hreflang + og:imagesrc/app/[locale]/[slug]/page.tsx— hreflang (conditional на repo exists)src/app/robots.ts— disallow правилаnext.config.ts— убрать legacy redirects (перенести в middleware), добавитьtrailingSlash: falsesrc/middleware.ts— legacy redirects (если #29 удалил, добавить обратно живые с 308) — КООРДИНАЦИЯ с issue #29src/__tests__/middleware.test.ts— тесты legacy redirectsВне scope
/RU(отдельная issue)/__404__soft 404 fix (issue #29)Критерии приемки
/ru/appsсодержит<link rel="alternate" hreflang="ru" href="https://slaid098.dev/ru/apps">/ru/appsсодержит<link rel="alternate" hreflang="en" href="https://slaid098.dev/en/apps">/ru/appsсодержит<link rel="alternate" hreflang="x-default" href="https://slaid098.dev/ru/apps">/ruсодержит hreflang ru/en/x-default/ru/opencode-configсодержит hreflang ru/en (counterpart)curl -sI https://slaid098.dev/youtube→ 308 →/ru/yt-video-downloader(один Location header, не цепочка)curl -sI https://slaid098.dev/take_break→ 308 →/ru/take-breakcurl -sI https://slaid098.dev/support→ 308 →/ru/contacts/ru/appsmeta propertyog:url=https://slaid098.dev/ru/apps/ru/appsmeta propertyog:image=https://slaid098.dev/og-image.png/ru/contactsmetaog:urlиog:imageприсутствуют/ru/subscriptionsmetaog:urlиog:imageприсутствуютcurl -s https://slaid098.dev/robots.txtсодержитDisallow: /api//ru/appsJSON-LD@type=CollectionPage,@id=https://slaid098.dev/ru/apps/ru/appsНЕ содержитPersonJSON-LD (только на home)curl -sI https://slaid098.dev/ru/apps/→ redirect на/ru/apps(trailingSlash: false)npm run lint,npm run typecheck,npm testпроходят