11---
22import Layout from ' @/layouts/Layout.astro' ;
33import { type CollectionEntry , getCollection } from ' astro:content' ;
4+ import { blogPost } from ' @/schema.blog' ;
45
56export async function getStaticPaths() {
67 const posts = await getCollection (' posts' , ({ data }) => {
@@ -15,6 +16,60 @@ type Props = CollectionEntry<'posts'>;
1516
1617const post = Astro .props ;
1718const { Content } = await post .render ();
19+
20+ // Prepare data for JSON-LD
21+ const postUrl = ` https://blog.x7md.net/posts/${post .slug }/ ` ;
22+ const postContent = await post .render ();
23+
24+ // Extract article body text from markdown content
25+ // Remove markdown syntax and get clean text for schema.org
26+ function extractTextFromMarkdown(markdown ) {
27+ if (! markdown ) return ' ' ;
28+
29+ // Remove markdown headers, links, code blocks, etc.
30+ let text = markdown
31+ .replace (/ ^ #{1,6} \s + / gm , ' ' ) // Remove headers
32+ .replace (/ \[ ([^ \] ] + )\]\( [^ )] + \) / g , ' $1' ) // Convert links to text
33+ .replace (/ ```[\s\S ] *? ```/ g , ' ' ) // Remove code blocks
34+ .replace (/ `([^ `] + )`/ g , ' $1' ) // Remove inline code
35+ .replace (/ \*\* ([^ *] + )\*\* / g , ' $1' ) // Remove bold
36+ .replace (/ \* ([^ *] + )\* / g , ' $1' ) // Remove italic
37+ .replace (/ !\[ ([^ \] ] * )\]\( [^ )] + \) / g , ' ' ) // Remove images
38+ .replace (/ ^ \s * [-*+] \s + / gm , ' ' ) // Remove list markers
39+ .replace (/ ^ \s * \d + \. \s + / gm , ' ' ) // Remove numbered list markers
40+ .replace (/ ^ \s * >\s + / gm , ' ' ) // Remove blockquotes
41+ .replace (/ \n {3,} / g , ' \n\n ' ) // Normalize line breaks
42+ .trim ();
43+
44+ // Truncate to reasonable length for schema.org (first 500 words)
45+ const words = text .split (/ \s + / ).filter (word => word .length > 0 );
46+ if (words .length > 500 ) {
47+ text = words .slice (0 , 500 ).join (' ' ) + ' ...' ;
48+ }
49+
50+ return text ;
51+ }
52+
53+ const articleBodyText = extractTextFromMarkdown (post .body ) || post .data .description ;
54+
55+ // Count words in the content (more accurate)
56+ const wordCount = post .body ? post .body .split (/ \s + / ).filter (word => word .trim ().length > 0 ).length : 0 ;
57+
58+ // Calculate reading time (average 200 words per minute)
59+ const readingTimeMinutes = Math .ceil (wordCount / 200 );
60+
61+ const jsonLdData = {
62+ url: postUrl ,
63+ headline: post .data .title ,
64+ description: post .data .description ,
65+ articleBody: articleBodyText ,
66+ datePublished: post .data .pubDate .toISOString (),
67+ dateModified: post .data .updatedDate ? post .data .updatedDate .toISOString () : post .data .pubDate .toISOString (),
68+ image: post .data .image ? ` https://blog.x7md.net${post .data .image } ` : undefined ,
69+ keywords: [... (post .data .tags || []), ... (post .data .keyword || [])],
70+ wordCount: wordCount ,
71+ timeRequired: ` PT${readingTimeMinutes }M ` // ISO 8601 duration format
72+ };
1873---
1974
2075<Layout title ={ post .data .title } description ={ post .data .description } >
@@ -45,4 +100,7 @@ const { Content } = await post.render();
45100 <Content />
46101 </article >
47102 </main >
103+
104+ <!-- JSON-LD Structured Data -->
105+ <script type =" application/ld+json" set:html ={ blogPost (jsonLdData )} ></script >
48106</Layout >
0 commit comments