@@ -17,10 +17,10 @@ function Section({ jsonLD, ...props }: Props) {
1717 const canonical = props . canonical
1818 ? props . canonical
1919 : jsonLD ?. seo ?. canonical
20- ? jsonLD . seo . canonical
21- : jsonLD ?. breadcrumb
22- ? canonicalFromBreadcrumblist ( jsonLD ?. breadcrumb )
23- : undefined ;
20+ ? jsonLD . seo . canonical
21+ : jsonLD ?. breadcrumb
22+ ? canonicalFromBreadcrumblist ( jsonLD ?. breadcrumb )
23+ : undefined ;
2424
2525 const noIndexing = props . noIndexing ||
2626 jsonLD ?. seo ?. noIndexing ||
@@ -43,70 +43,149 @@ function Section({ jsonLD, ...props }: Props) {
4343 ] ;
4444
4545 if ( Array . isArray ( obj ) ) {
46- return obj . map ( ( item ) => sanitizeObj ( item ) ) as T ;
46+ return obj . map ( item => sanitizeObj ( item ) ) as T ;
4747 }
4848
49- if ( typeof obj === " object" && obj !== null ) {
49+ if ( typeof obj === ' object' && obj !== null ) {
5050 return Object . fromEntries (
5151 Object . entries ( obj )
5252 . filter ( ( [ key ] ) => ! propsToRemove . includes ( key ) )
53- . map ( ( [ key , value ] ) => [ key , sanitizeObj ( value ) ] ) ,
53+ . map ( ( [ key , value ] ) => [ key , sanitizeObj ( value ) ] )
5454 ) as T ;
5555 }
5656
5757 return obj ;
5858 }
5959
6060 function formatProductListing ( data : ProductListingPage | null ) {
61- if ( ! data ) return null ;
62-
63- const itemListElement : ProductListingPageListItem [ ] = data . products . reduce (
64- ( accu : ProductListingPageListItem [ ] , product , index ) => {
65- accu . push ( {
66- "@type" : "ListItem" ,
61+ if ( ! data || ! data . products || data . products . length === 0 ) return null ;
62+
63+
64+ const itemListElement : ProductListingPageListItem [ ] = data . products
65+ . filter ( product => product && product . sku )
66+ . map ( ( product , index ) => {
67+
68+ const sanitizedProduct = sanitizeObj ( product ) ;
69+
70+
71+ if ( typeof sanitizedProduct === 'object' && sanitizedProduct !== null ) {
72+
73+ const productWithDefaults = {
74+ ...sanitizedProduct ,
75+ "@type" : sanitizedProduct [ "@type" ] || "Product" ,
76+
77+ url : sanitizedProduct . url || `#product-${ product . sku } `
78+ } ;
79+
80+ return {
81+ "@type" : "ListItem" as const ,
82+ position : index + 1 ,
83+ item : productWithDefaults
84+ } ;
85+ }
86+
87+ return {
88+ "@type" : "ListItem" as const ,
6789 position : index + 1 ,
68- item : sanitizeObj ( product ) ,
69- } ) ;
70- return accu ;
71- } ,
72- [ ] ,
73- ) ;
90+ item : sanitizedProduct
91+ } ;
92+ } ) ;
7493
94+
7595 return {
76- "@type" : "ItemList" ,
77- "itemListElement" : itemListElement ,
96+ "@context" : "https://schema.org" ,
97+ "@type" : "ItemList" as const ,
98+ "numberOfItems" : itemListElement . length ,
99+ "itemListElement" : itemListElement
78100 } ;
79101 }
80102
81103 function formatBreadCrumb ( data : ProductListingPage | null ) {
82- if ( ! data ) return null ;
104+ if ( ! data || ! data . breadcrumb || ! data . breadcrumb . itemListElement ) return null ;
105+
106+
107+ const validItems = data . breadcrumb . itemListElement
108+ . filter ( item => item && item . name && item . item )
109+ . map ( ( item , index ) => ( {
110+ "@type" : "ListItem" as const ,
111+ position : index + 1 ,
112+ name : item . name ,
113+ item : item . item
114+ } ) ) ;
83115
116+ if ( validItems . length === 0 ) return null ;
117+
118+
84119 return {
85- "@type" : "BreadcrumbList" ,
86- "itemListElement" : data . breadcrumb . itemListElement ,
120+ "@context" : "https://schema.org" ,
121+ "@type" : "BreadcrumbList" as const ,
122+ "itemListElement" : validItems
87123 } ;
88124 }
89125
90126 function formatNewJsonLd ( data : ProductListingPage | null ) {
91- const items = [ formatProductListing ( data ) , formatBreadCrumb ( data ) ] . filter ( (
92- item ,
93- ) => item !== null && item !== undefined ) ;
94-
95- return [ { ...items , pageInfo : jsonLD ?. pageInfo , seo : jsonLD ?. seo } ] ;
127+ if ( ! data ) return [ ] ;
128+
129+
130+ const jsonLdArray = [ ] ;
131+
132+
133+ const productList = formatProductListing ( data ) ;
134+ if ( productList ) {
135+ jsonLdArray . push ( productList ) ;
136+ }
137+
138+
139+ const breadcrumb = formatBreadCrumb ( data ) ;
140+ if ( breadcrumb ) {
141+ jsonLdArray . push ( breadcrumb ) ;
142+ }
143+
144+
145+ if ( jsonLdArray . length > 0 && ( data . seo || data . pageInfo ) ) {
146+ const webPageSchema = {
147+ "@context" : "https://schema.org" ,
148+ "@type" : "CollectionPage" ,
149+ ...( data . seo ?. title && { "name" : data . seo . title } ) ,
150+ ...( data . seo ?. description && { "description" : data . seo . description } ) ,
151+ ...( data . seo ?. canonical && { "url" : data . seo . canonical } ) ,
152+
153+ ...( productList && { "mainEntity" : productList } ) ,
154+ ...( breadcrumb && { "breadcrumb" : breadcrumb } )
155+ } ;
156+
157+
158+ return [ webPageSchema ] ;
159+ }
160+
161+ return jsonLdArray ;
96162 }
97163
164+
98165 const newJsonLd = formatNewJsonLd ( jsonLD ) ;
166+
167+
168+ const validJsonLd = newJsonLd . filter ( item => {
169+ try {
170+
171+ JSON . stringify ( item ) ;
172+ return true ;
173+ } catch {
174+ console . error ( "Invalid JSON-LD item:" , item ) ;
175+ return false ;
176+ }
177+ } ) ;
99178
100179 return (
101180 < Seo
102181 { ...props }
103182 title = { title || props . title }
104183 description = { description || props . description }
105184 canonical = { canonical }
106- jsonLDs = { newJsonLd }
185+ jsonLDs = { validJsonLd }
107186 noIndexing = { noIndexing }
108187 />
109188 ) ;
110189}
111190
112- export default Section ;
191+ export default Section ;
0 commit comments