Files
avbeat-com/src/app/articles/[slug]/page.tsx

114 lines
3.5 KiB
TypeScript

import type { Metadata } from "next";
import { redirect } from "next/navigation";
import ArticleDetailPage from "./ArticleDetailClient";
import type { Article } from "@/lib/articles/types";
import {
getLegacyArticleBySlug,
getLegacyRecentSlugs,
getLegacyRelatedArticles,
} from "@/lib/articles/legacy-source";
export const revalidate = 3600;
export const dynamicParams = true;
interface PageProps {
params: Promise<{ slug: string }>;
}
const SITE_URL =
process.env.NEXT_PUBLIC_SITE_URL || "https://broadcastbeat.com";
function toIso(d: string | null | undefined): string {
if (!d) return new Date().toISOString();
const parsed = new Date(d);
return isNaN(parsed.getTime()) ? new Date().toISOString() : parsed.toISOString();
}
function absoluteImage(src: string | undefined | null): string {
if (!src) return `${SITE_URL}/assets/images/article-placeholder.jpg`;
if (/^https?:\/\//i.test(src)) return src;
return `${SITE_URL}${src.startsWith("/") ? "" : "/"}${src}`;
}
async function loadArticle(slug: string): Promise<{
article: Article | null;
related: Article[];
}> {
const imported = await getLegacyArticleBySlug(slug);
if (!imported) return { article: null, related: [] };
const related = await getLegacyRelatedArticles(slug, imported.category, 3);
return { article: imported, related };
}
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { slug } = await params;
const { article } = await loadArticle(slug);
if (!article) {
return { title: "Article Not Found", description: "The article you are looking for does not exist." };
}
return {
title: article.title,
description: article.excerpt,
alternates: { canonical: `/articles/${article.slug}` },
openGraph: {
type: "article",
title: article.title,
description: article.excerpt,
url: `/articles/${article.slug}`,
images: [{ url: article.image, width: 1200, height: 630, alt: article.alt }],
authors: [article.author],
publishedTime: article.date,
tags: article.tags,
},
twitter: {
card: "summary_large_image",
title: article.title,
description: article.excerpt,
images: [article.image],
creator: "@Broadcast Beat",
},
};
}
export async function generateStaticParams() {
const recent = await getLegacyRecentSlugs(50);
return recent.map((slug) => ({ slug }));
}
export default async function ArticlePage({ params }: PageProps) {
const { slug } = await params;
const { article, related } = await loadArticle(slug);
if (!article) {
// Slug isn't in any of our article tables — send the reader to the news
// index so a click from the homepage/ticker doesn't dead-end on 404.
redirect("/news");
}
const articleSchema = {
"@context": "https://schema.org",
"@type": "Article",
headline: article.title,
description: article.excerpt,
image: [absoluteImage(article.image)],
author: { "@type": "Person", name: article.author },
publisher: {
"@type": "Organization",
name: "Broadcast Beat",
logo: { "@type": "ImageObject", url: `${SITE_URL}/assets/images/logo.png` },
},
datePublished: toIso(article.date),
dateModified: new Date().toISOString(),
mainEntityOfPage: { "@type": "WebPage", "@id": `${SITE_URL}/articles/${article.slug}` },
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(articleSchema) }}
/>
<ArticleDetailPage article={article} relatedArticles={related} />
</>
);
}