import type { Metadata } from 'next'; import Link from 'next/link'; import { notFound } from 'next/navigation'; import { getArticleFull, getRelatedArticles } from '@/lib/queries'; import RelatedArticles from '@/components/RelatedArticles'; import { InArticleAd, SidebarAd } from '@/components/ads/placements'; import { categoryCover, ogImageUrl, resolveCoverImage } from '@/lib/cover'; import { CoverImage } from '@/components/CoverImage'; import { env } from '@/lib/env'; import { formatFull } from '@/lib/format'; import { safeParse } from '@/lib/llm/parse'; import { SECTIONS } from '@/data/feeds'; export const dynamic = 'force-dynamic'; interface ArticleData { id: string; slug: string; title: string; headline: string | null; body: string | null; takeaways: string | null; tags: string | null; siteName: string; category: string; author: string | null; publishedAt: Date | null; synthesizedAt: Date | null; sourceUrl: string; canonicalUrl: string; image: string | null; sources: string | null; originalText: string | null; llmProvider: string | null; } export async function generateMetadata({ params, }: { params: { slug: string }; }): Promise { const a = await getArticleFull(params.slug); if (!a) return { title: 'Briefing not found' }; const title = a.headline ?? a.title; const description = a.body?.split('\n\n')[0]?.slice(0, 160) ?? ''; const sectionLabel = SECTIONS.find((s) => s.slug === a.category)?.label ?? 'Briefing'; return { title, description, alternates: { canonical: a.canonicalUrl }, openGraph: { type: 'article', title, description, url: a.canonicalUrl, siteName: env.siteName, publishedTime: a.publishedAt?.toISOString(), modifiedTime: a.synthesizedAt?.toISOString(), section: sectionLabel, tags: a.tags ? safeParse(a.tags).slice(0, 5) : undefined, images: [{ url: ogImageUrl(a.image, a.category), width: 1200, height: 630, alt: title, }], }, twitter: { card: 'summary_large_image', title, description, images: [ogImageUrl(a.image, a.category)], }, }; } export default async function ArticlePage({ params, }: { params: { slug: string }; }) { const a = await getArticleFull(params.slug); if (!a || !a.body) notFound(); const paragraphs = a.body.split('\n\n').map((p) => p.trim()).filter(Boolean); const takeaways = a.takeaways ? safeParse(a.takeaways) : []; const tags = a.tags ? safeParse(a.tags) : []; const relatedItems = await getRelatedArticles(a.slug, 6); const title = a.headline ?? a.title; return (
{/* Article column */}

{SECTIONS.find((s) => s.slug === a.category)?.label ?? 'Briefing'} {a.publishedAt && ( · {formatFull(a.publishedAt.toISOString())} )}

{title}

{takeaways.length > 0 && (

Key takeaways

    {takeaways.map((t, i) => (
  • {t}
  • ))}
)}
{paragraphs.slice(0, 1).map((p, i) => (

{p}

))} {/* In-article ad: between rewritten paragraphs 1 and 2 */} {paragraphs.slice(1).map((p, i) => (

{p}

))}
{tags.length > 0 && (
{tags.map((t) => ( #{t} ))}
)}
{/* Sidebar */}
); }