fix(seo): убрать дубли title и description
Some checks failed
deploy / deploy (push) Successful in 1m24s
security / security (push) Has been cancelled

- 6 pages (o-nas, nashi-druzya, 4 миры) получили свой description в frontmatter;
  раньше [slug].astro для типа page передавал description=undefined → fallback
  на SITE_DESCRIPTION даёт 6 одинаковых meta-description.
- [slug].astro: для постов в SEO-title подставляется год публикации (или
  полная дата, если есть второй пост с тем же title в том же году). Покрывает
  дубли «Внимание! Технические работы!» (×5) и «С 23 февраля!» (×2).
- index.astro: свой description для главной (вместо SITE_DESCRIPTION).
- category/[slug].astro: вычисляемый description с количеством публикаций и
  диапазоном лет — на случай если категории попадут в индексацию.
This commit is contained in:
Dmitry Gusev
2026-05-30 02:22:33 +03:00
parent 048cb673e0
commit b40c377761
9 changed files with 45 additions and 7 deletions

View File

@@ -6,13 +6,27 @@ import { CATEGORY_COLORS } from '../consts';
export async function getStaticPaths() {
const posts = await getCollection('posts');
const pages = await getCollection('pages');
// Считаем сколько постов с одинаковым {title, year} — если >1, в SEO-title
// подставим полную дату, иначе только год.
const titleYearCount = new Map<string, number>();
for (const p of posts) {
const key = `${p.data.title}|${p.data.pubDate.getFullYear()}`;
titleYearCount.set(key, (titleYearCount.get(key) ?? 0) + 1);
}
return [
...posts.map((p) => ({ params: { slug: p.data.slug }, props: { entry: p, kind: 'post' as const } })),
...pages.map((p) => ({ params: { slug: p.data.slug }, props: { entry: p, kind: 'page' as const } })),
...posts.map((p) => {
const year = p.data.pubDate.getFullYear();
const key = `${p.data.title}|${year}`;
const sameYearDup = (titleYearCount.get(key) ?? 1) > 1;
return { params: { slug: p.data.slug }, props: { entry: p, kind: 'post' as const, sameYearDup } };
}),
...pages.map((p) => ({ params: { slug: p.data.slug }, props: { entry: p, kind: 'page' as const, sameYearDup: false } })),
];
}
const { entry, kind } = Astro.props;
const { entry, kind, sameYearDup } = Astro.props;
const { Content } = await render(entry);
// Буквица — только для постов с телом длиннее короткого порога.
const bodyLen = entry.body?.length ?? 0;
@@ -20,10 +34,18 @@ const useDropCap = kind === 'post' && bodyLen > 240;
const fmtDate = (d: Date) =>
d.toLocaleDateString('ru-RU', { year: 'numeric', month: 'long', day: 'numeric' });
// Короткая дата без "г." для SEO-title постов с одинаковым названием в одном году.
const fmtDateForTitle = (d: Date) =>
d.toLocaleDateString('ru-RU', { year: 'numeric', month: 'long', day: 'numeric' }).replace(/\sг\.?$/, '');
const seoTitle = kind === 'post'
? `${entry.data.title} (${sameYearDup ? fmtDateForTitle(entry.data.pubDate) : entry.data.pubDate.getFullYear()})`
: entry.data.title;
---
<BaseLayout
title={entry.data.title}
description={kind === 'post' ? entry.data.description : undefined}
title={seoTitle}
description={entry.data.description || undefined}
ogType={kind === 'post' ? 'article' : 'website'}
>
<article class="post">