Files
zeropost-web/app/rss.xml/route.js
T
Alexey Pavlov 4702614896 feat: мобильная версия + поиск + SEO-инфраструктура
Мобилка:
- Header: hide-on-scroll, мобильный burger-menu, тонкая адаптация
- Hero: текст и кнопки оптимизированы под узкие экраны (full-width buttons)
- ArticleCard featured: на мобилке в столбик, картинка сверху
- Stats: компактная сетка 2x2 с уменьшенным шрифтом
- Глобально: scroll-behavior smooth, safe-area-inset, tap targets 40px+
- prefers-reduced-motion respected

Страница статьи:
- ReadingProgress: прогресс-бар сверху при скролле
- ScrollToTop: круглая кнопка наверху после 800px скролла
- ShareButton: Web Share API на мобилках, копирование URL на десктопе
- Related articles: подбираем по пересечению тегов (max 3)
- Мобильная типографика: prose-base sm:prose-lg, leading-relaxed

SEO/инфра:
- /api/search: простой поиск по title/excerpt/tags с подсветкой и скорингом
- SearchBox: оверлей с / хоткеем, дебаунс 250ms, мобиле-friendly
- /rss.xml: полноценный RSS-фид
- sitemap.xml: динамический через next sitemap()
- robots.txt: динамический
- viewport metadata + theme-color для светлой/тёмной темы
- alternates rel=alternate type=application/rss+xml
2026-05-31 09:43:11 +03:00

45 lines
1.3 KiB
JavaScript

import { listArticles } from '@/lib/engine';
const SITE = 'https://zeropost.ru';
function escapeXml(s = '') {
return s
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
}
export async function GET() {
const articles = await listArticles({ limit: 50 });
const items = articles.map(a => `
<item>
<title>${escapeXml(a.title)}</title>
<link>${SITE}/blog/${a.slug}</link>
<guid isPermaLink="true">${SITE}/blog/${a.slug}</guid>
<pubDate>${new Date(a.published_at).toUTCString()}</pubDate>
<description>${escapeXml(a.excerpt || '')}</description>
${(a.tags || []).map(t => `<category>${escapeXml(t)}</category>`).join('')}
</item>`).join('');
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>ZeroPost</title>
<link>${SITE}</link>
<description>Блог про практическое применение ИИ</description>
<language>ru</language>
<atom:link href="${SITE}/rss.xml" rel="self" type="application/rss+xml"/>
${items}
</channel>
</rss>`;
return new Response(xml, {
headers: {
'Content-Type': 'application/rss+xml; charset=utf-8',
'Cache-Control': 'public, max-age=600, s-maxage=600',
},
});
}