Files
anotherreflections-website-v2/src/pages/[slug].astro
Dmitry Gusev b40c377761
Some checks failed
deploy / deploy (push) Successful in 1m24s
security / security (push) Has been cancelled
fix(seo): убрать дубли title и description
- 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 с количеством публикаций и
  диапазоном лет — на случай если категории попадут в индексацию.
2026-05-30 02:22:33 +03:00

80 lines
2.9 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
import { getCollection, render } from 'astro:content';
import BaseLayout from '../layouts/BaseLayout.astro';
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) => {
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, sameYearDup } = Astro.props;
const { Content } = await render(entry);
// Буквица — только для постов с телом длиннее короткого порога.
const bodyLen = entry.body?.length ?? 0;
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={seoTitle}
description={entry.data.description || undefined}
ogType={kind === 'post' ? 'article' : 'website'}
>
<article class="post">
<header>
{kind === 'post' && entry.data.categories.length > 0 && (
<a
href={`/category/${entry.data.categorySlugs[0]}/`}
class="cat-tag"
style={`--cat-color: ${CATEGORY_COLORS[entry.data.categorySlugs[0]] ?? 'var(--accent)'}`}
>
{entry.data.categories[0]}
</a>
)}
<h1>{entry.data.title}</h1>
{kind === 'post' && (
<div class="post-meta">
<time datetime={entry.data.pubDate.toISOString()}>{fmtDate(entry.data.pubDate)}</time>
</div>
)}
</header>
<div class={`post-body ${useDropCap ? 'has-dropcap' : ''}`}>
<Content />
</div>
</article>
{kind === 'post' && (
<nav class="pagination">
<a href="/">← Вернуться в хронику</a>
</nav>
)}
</BaseLayout>