feat: complete MDX migration, stabilize environment & verify via E2E tests

Former-commit-id: ec3e64156a2e182535cbdcf0d975cd54603a517d
This commit is contained in:
2026-05-03 18:46:41 +02:00
parent e57661a586
commit b13f628b55
131 changed files with 1388 additions and 25288 deletions

View File

@@ -5,7 +5,7 @@ import {
getPostBySlug,
getAdjacentPosts,
getReadingTime,
extractLexicalHeadings,
getHeadings,
} from '@/lib/blog';
import { Metadata } from 'next';
import Link from 'next/link';
@@ -16,9 +16,7 @@ import TableOfContents from '@/components/blog/TableOfContents';
import { Heading } from '@/components/ui';
import { setRequestLocale } from 'next-intl/server';
import BlogEngagementTracker from '@/components/analytics/BlogEngagementTracker';
// Payload CMS Imports
import PayloadRichText from '@/components/PayloadRichText';
import { MDXRemote } from 'next-mdx-remote/rsc';
interface BlogPostProps {
params: Promise<{
@@ -65,20 +63,9 @@ export default async function BlogPost({ params }: BlogPostProps) {
notFound();
}
// If the user accessed this post using a slug from a different locale
// (e.g. via the generic language switcher), redirect them to the correct localized slug URL
if (post.slug && post.slug !== slug) {
redirect(`/${locale}/blog/${post.slug}`);
}
const { prev, next, isPrevRandom, isNextRandom } = await getAdjacentPosts(post.slug, locale);
// Convert Lexical content into a plain string to estimate reading time roughly
// Extract headings for TOC
const headings = extractLexicalHeadings(post.content?.root || post.content);
// Convert Lexical content into a plain string to estimate reading time roughly
const rawTextContent = JSON.stringify(post.content);
const headings = getHeadings(post.content);
const readingTime = getReadingTime(post.content);
return (
<article className="bg-white min-h-screen font-sans selection:bg-primary/10 selection:text-primary">
@@ -86,7 +73,7 @@ export default async function BlogPost({ params }: BlogPostProps) {
title={post.frontmatter.title}
slug={slug}
category={post.frontmatter.category}
readingTime={getReadingTime(rawTextContent)}
readingTime={readingTime}
/>
{/* Featured Image Header */}
@@ -131,7 +118,7 @@ export default async function BlogPost({ params }: BlogPostProps) {
})}
</time>
<span className="w-1 h-1 bg-white/30 rounded-full" />
<span>{getReadingTime(rawTextContent)} min read</span>
<span>{readingTime} min read</span>
{(new Date(post.frontmatter.date) > new Date() ||
post.frontmatter.public === false) && (
<>
@@ -168,7 +155,7 @@ export default async function BlogPost({ params }: BlogPostProps) {
})}
</time>
<span className="w-1 h-1 bg-neutral-400 rounded-full" />
<span>{getReadingTime(rawTextContent)} min read</span>
<span>{readingTime} min read</span>
{(new Date(post.frontmatter.date) > new Date() ||
post.frontmatter.public === false) && (
<>
@@ -197,9 +184,9 @@ export default async function BlogPost({ params }: BlogPostProps) {
</div>
)}
{/* Main content with enhanced styling rendering Payload Lexical */}
{/* Main content */}
<div className="prose prose-lg md:prose-xl max-w-none prose-headings:font-bold prose-headings:text-text-primary prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-img:rounded-2xl prose-img:shadow-2xl prose-blockquote:border-primary prose-blockquote:bg-primary/5 prose-blockquote:rounded-r-2xl prose-strong:text-primary">
<PayloadRichText data={post.content} />
<MDXRemote source={post.content} />
</div>
{/* Power CTA */}
@@ -284,8 +271,8 @@ export default async function BlogPost({ params }: BlogPostProps) {
'@id': `${SITE_URL}/${locale}/blog/${slug}`,
},
articleSection: post.frontmatter.category,
wordCount: rawTextContent.split(/\s+/).length,
timeRequired: `PT${getReadingTime(rawTextContent)}M`,
wordCount: post.content.split(/\s+/).length,
timeRequired: `PT${readingTime}M`,
} as any
}
/>