feat: zeropost-web — публичный AI-блог на zeropost.ru

- Next.js 16, Tailwind с @tailwindcss/typography
- Главная: hero, featured-статья, сетка карточек, облако тегов
- /blog/[slug]: статья со SSG + revalidate 60s, prose typography
- /tag/[name]: фильтр по тегам
- /about: про проект
- /api/cron/generate: endpoint для авто-генерации (защищён x-cron-token)
- SEO: dynamic metadata, OG, sitemap-ready
- Лента грузится с zeropost-engine /api/articles
This commit is contained in:
Alexey Pavlov
2026-05-31 08:50:35 +03:00
parent adea4b80de
commit 6dfe8b8afa
20 changed files with 2913 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
import Link from 'next/link';
import Header from '@/components/Header';
import Footer from '@/components/Footer';
import ArticleCard from '@/components/ArticleCard';
import { listArticles } from '@/lib/engine';
import { ArrowLeft } from 'lucide-react';
export const revalidate = 60;
export async function generateMetadata({ params }) {
const { name } = await params;
return { title: `Статьи по теме #${decodeURIComponent(name)}` };
}
export default async function TagPage({ params }) {
const { name } = await params;
const tag = decodeURIComponent(name);
const articles = await listArticles({ tag, limit: 50 });
return (
<>
<Header />
<main className="container-wide pt-10 pb-16">
<Link href="/" className="btn-ghost text-sm mb-4 -ml-2">
<ArrowLeft className="w-4 h-4" /> Все статьи
</Link>
<h1 className="text-3xl sm:text-4xl font-bold mb-2">#{tag}</h1>
<p className="text-mute mb-8">{articles.length} {articles.length === 1 ? 'материал' : 'материалов'}</p>
{articles.length === 0 ? (
<p className="text-mute">Пока пусто.</p>
) : (
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
{articles.map(a => <ArticleCard key={a.id} article={a} />)}
</div>
)}
</main>
<Footer />
</>
);
}