11import { SITE_NAME , SITE_URL } from '@/lib/site-seo' ;
22
3+ export type ArticleFaq = { q : string ; a : string } ;
4+
35type Props = {
46 headline : string ;
57 description : string ;
68 datePublished ?: string ;
79 url : string ;
8- type ?: 'BlogPosting' | 'Article' ;
10+ /** TechArticle for job-support pages; BlogPosting for general blog content. */
11+ type ?: 'BlogPosting' | 'Article' | 'TechArticle' ;
12+ /** Technology / topic this article teaches — used in TechArticle `about` and `teaches`. */
13+ about ?: string ;
14+ /** FAQs to emit as an inline FAQPage schema alongside the article schema. */
15+ faqs ?: ArticleFaq [ ] ;
916} ;
1017
1118export default function ArticleStructuredData ( {
@@ -14,25 +21,81 @@ export default function ArticleStructuredData({
1421 datePublished,
1522 url,
1623 type = 'BlogPosting' ,
24+ about,
25+ faqs,
1726} : Props ) {
18- const data = {
27+ const published = datePublished ? `${ datePublished } T12:00:00+05:30` : undefined ;
28+
29+ const articleSchema : Record < string , unknown > = {
1930 '@context' : 'https://schema.org' ,
2031 '@type' : type ,
2132 headline,
2233 description,
2334 url,
2435 mainEntityOfPage : { '@type' : 'WebPage' , '@id' : url } ,
25- author : { '@type' : 'Organization' , name : SITE_NAME } ,
36+ author : {
37+ '@type' : 'Organization' ,
38+ name : SITE_NAME ,
39+ url : SITE_URL ,
40+ } ,
2641 publisher : {
2742 '@type' : 'Organization' ,
2843 name : SITE_NAME ,
44+ url : SITE_URL ,
2945 logo : { '@type' : 'ImageObject' , url : `${ SITE_URL } /images/logo.png` } ,
3046 } ,
31- ...( datePublished
32- ? { datePublished : `${ datePublished } T12:00:00+05:30` , dateModified : `${ datePublished } T12:00:00+05:30` }
33- : { } ) ,
3447 image : `${ SITE_URL } /images/previewimg.png` ,
48+ ...( published ? { datePublished : published , dateModified : published } : { } ) ,
49+ /**
50+ * Speakable specification — tells AI agents and voice assistants which
51+ * CSS selectors contain the most important extractable content.
52+ * h1 = canonical subject; h2 = section labels; .post-content = body text.
53+ */
54+ speakable : {
55+ '@type' : 'SpeakableSpecification' ,
56+ cssSelector : [ 'h1' , 'h2' , 'h3' , '.post-content' ] ,
57+ } ,
3558 } ;
3659
37- return < script type = "application/ld+json" dangerouslySetInnerHTML = { { __html : JSON . stringify ( data ) } } /> ;
60+ // TechArticle-specific properties
61+ if ( type === 'TechArticle' ) {
62+ articleSchema . proficiencyLevel = 'Expert' ;
63+ articleSchema . articleSection = 'IT Job Support' ;
64+ if ( about ) {
65+ articleSchema . about = {
66+ '@type' : 'Thing' ,
67+ name : about ,
68+ } ;
69+ articleSchema . teaches = about ;
70+ }
71+ articleSchema . inLanguage = 'en-US' ;
72+ articleSchema . isAccessibleForFree = true ;
73+ }
74+
75+ const faqSchema = faqs && faqs . length > 0
76+ ? {
77+ '@context' : 'https://schema.org' ,
78+ '@type' : 'FAQPage' ,
79+ mainEntity : faqs . map ( ( f ) => ( {
80+ '@type' : 'Question' ,
81+ name : f . q ,
82+ acceptedAnswer : { '@type' : 'Answer' , text : f . a } ,
83+ } ) ) ,
84+ }
85+ : null ;
86+
87+ return (
88+ < >
89+ < script
90+ type = "application/ld+json"
91+ dangerouslySetInnerHTML = { { __html : JSON . stringify ( articleSchema ) } }
92+ />
93+ { faqSchema && (
94+ < script
95+ type = "application/ld+json"
96+ dangerouslySetInnerHTML = { { __html : JSON . stringify ( faqSchema ) } }
97+ />
98+ ) }
99+ </ >
100+ ) ;
38101}
0 commit comments