fix(seo): strip HTML from meta description + OG/Twitter/Schema.org
Article excerpts sometimes contain raw <p> tags; Next.js HTML-escapes them into the page <meta>, surfacing as literal '<p>' in view-source and breaking SEO previews. cleanExcerpt() strips tags + entities for all plain-text meta surfaces. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { Metadata } from "next";
|
||||
import { cleanExcerpt } from "@/lib/articles/excerpt";
|
||||
import { redirect } from "next/navigation";
|
||||
import ArticleDetailPage from "./ArticleDetailClient";
|
||||
import type { Article } from "@/lib/articles/types";
|
||||
@@ -53,12 +54,12 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
}
|
||||
return {
|
||||
title: article.title,
|
||||
description: article.excerpt,
|
||||
description: cleanExcerpt(article.excerpt),
|
||||
alternates: { canonical: `/articles/${article.slug}` },
|
||||
openGraph: {
|
||||
type: "article",
|
||||
title: article.title,
|
||||
description: article.excerpt,
|
||||
description: cleanExcerpt(article.excerpt),
|
||||
url: `/articles/${article.slug}`,
|
||||
images: [{ url: article.image, width: 1200, height: 630, alt: article.alt }],
|
||||
authors: [article.author],
|
||||
@@ -68,7 +69,7 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: article.title,
|
||||
description: article.excerpt,
|
||||
description: cleanExcerpt(article.excerpt),
|
||||
images: [article.image],
|
||||
creator: "@AV Beat",
|
||||
},
|
||||
@@ -93,7 +94,7 @@ export default async function ArticlePage({ params }: PageProps) {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
headline: article.title,
|
||||
description: article.excerpt,
|
||||
description: cleanExcerpt(article.excerpt),
|
||||
image: [absoluteImage(article.image)],
|
||||
author: { "@type": "Person", name: article.author },
|
||||
publisher: {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
getRelatedForumThreads, type RelatedForumThread,
|
||||
} from "@/lib/articles/legacy-source";
|
||||
import { linkifyAndExtractMentions } from "@/lib/company-mentions";
|
||||
import { cleanExcerpt } from "@/lib/articles/excerpt";
|
||||
import CompanyMentionsHover from "@/components/CompanyMentionsHover";
|
||||
import CompaniesInThisStory from "@/components/CompaniesInThisStory";
|
||||
import NewsArticleDetailClient from "./NewsArticleDetailClient";
|
||||
@@ -58,14 +59,19 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
if (!article) {
|
||||
return { title: "Article Not Found", description: "The article you are looking for does not exist." };
|
||||
}
|
||||
// Strip HTML from excerpt before stuffing into <meta description> /
|
||||
// openGraph / twitter — those MUST be plain text. Raw excerpts sometimes
|
||||
// contain <p>...</p>, which Next.js then HTML-escapes, surfacing as
|
||||
// literal `<p>` in page source.
|
||||
const cleanDesc = cleanExcerpt(article.excerpt);
|
||||
return {
|
||||
title: `${article.title} — AV Beat`,
|
||||
description: article.excerpt,
|
||||
description: cleanDesc,
|
||||
alternates: { canonical: `/news/${article.slug}` },
|
||||
openGraph: {
|
||||
type: "article",
|
||||
title: article.title,
|
||||
description: article.excerpt,
|
||||
description: cleanDesc,
|
||||
url: `/news/${article.slug}`,
|
||||
images: [{ url: article.image, width: 1200, height: 630, alt: article.alt }],
|
||||
authors: [article.author],
|
||||
@@ -75,7 +81,7 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: article.title,
|
||||
description: article.excerpt,
|
||||
description: cleanDesc,
|
||||
images: [article.image],
|
||||
creator: "@AV Beat",
|
||||
},
|
||||
@@ -106,7 +112,7 @@ export default async function NewsArticlePage({ params }: PageProps) {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "NewsArticle",
|
||||
headline: article.title,
|
||||
description: article.excerpt,
|
||||
description: cleanExcerpt(article.excerpt),
|
||||
image: [absoluteImage(article.image)],
|
||||
author: { "@type": "Person", name: article.author },
|
||||
publisher: {
|
||||
|
||||
Reference in New Issue
Block a user