@masters-ws/react-seo 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,11 +20,20 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/core/index.ts
21
21
  var core_exports = {};
22
22
  __export(core_exports, {
23
+ JsonLd: () => JsonLd,
24
+ cleanSchema: () => cleanSchema,
25
+ generateArticleMetadata: () => generateArticleMetadata,
23
26
  generateArticleSchema: () => generateArticleSchema,
24
27
  generateBookSchema: () => generateBookSchema,
25
28
  generateBreadcrumbSchema: () => generateBreadcrumbSchema,
29
+ generateCategoryMetadata: () => generateCategoryMetadata,
30
+ generateCollectionPageSchema: () => generateCollectionPageSchema,
26
31
  generateEventSchema: () => generateEventSchema,
27
32
  generateFAQSchema: () => generateFAQSchema,
33
+ generateHomepageMetadata: () => generateHomepageMetadata,
34
+ generateHowToSchema: () => generateHowToSchema,
35
+ generateItemListSchema: () => generateItemListSchema,
36
+ generateJobPostingSchema: () => generateJobPostingSchema,
28
37
  generateLocalBusinessSchema: () => generateLocalBusinessSchema,
29
38
  generateMovieSchema: () => generateMovieSchema,
30
39
  generateOrganizationSchema: () => generateOrganizationSchema,
@@ -32,82 +41,266 @@ __export(core_exports, {
32
41
  generatePaginationLinks: () => generatePaginationLinks,
33
42
  generatePodcastEpisodeSchema: () => generatePodcastEpisodeSchema,
34
43
  generatePodcastSchema: () => generatePodcastSchema,
44
+ generateProductMetadata: () => generateProductMetadata,
35
45
  generateProductSchema: () => generateProductSchema,
46
+ generateRecipeSchema: () => generateRecipeSchema,
36
47
  generateSoftwareSchema: () => generateSoftwareSchema,
37
48
  generateVideoSchema: () => generateVideoSchema,
49
+ generateWebPageSchema: () => generateWebPageSchema,
38
50
  generateWebSiteSchema: () => generateWebSiteSchema,
39
- toNextMetadata: () => toNextMetadata
51
+ toNextMetadata: () => toNextMetadata,
52
+ validateSEO: () => validateSEO
40
53
  });
41
54
  module.exports = __toCommonJS(core_exports);
42
55
 
56
+ // src/core/utils.ts
57
+ function cleanSchema(obj) {
58
+ if (Array.isArray(obj)) {
59
+ return obj.filter((item) => item !== void 0 && item !== null).map((item) => typeof item === "object" ? cleanSchema(item) : item);
60
+ }
61
+ const cleaned = {};
62
+ for (const [key, value] of Object.entries(obj)) {
63
+ if (value === void 0 || value === null) continue;
64
+ if (Array.isArray(value)) {
65
+ const cleanedArr = value.filter((item) => item !== void 0 && item !== null).map((item) => typeof item === "object" && item !== null ? cleanSchema(item) : item);
66
+ if (cleanedArr.length > 0) {
67
+ cleaned[key] = cleanedArr;
68
+ }
69
+ } else if (typeof value === "object") {
70
+ cleaned[key] = cleanSchema(value);
71
+ } else {
72
+ cleaned[key] = value;
73
+ }
74
+ }
75
+ return cleaned;
76
+ }
77
+ function validateSEO(schemaType, data, requiredFields) {
78
+ const warnings = [];
79
+ for (const field of requiredFields) {
80
+ const value = data[field];
81
+ if (value === void 0 || value === null || value === "") {
82
+ warnings.push(`[react-seo] Warning: "${field}" is missing in ${schemaType} schema. Google may not show rich results.`);
83
+ }
84
+ }
85
+ if (warnings.length > 0 && typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" && globalThis.process.env?.NODE_ENV !== "production") {
86
+ warnings.forEach((w) => console.warn(w));
87
+ }
88
+ return warnings;
89
+ }
90
+
43
91
  // src/core/schemas.ts
44
92
  function generateOrganizationSchema(config) {
45
- return {
93
+ return cleanSchema({
46
94
  "@context": "https://schema.org",
47
95
  "@type": "Organization",
48
96
  "name": config.name,
49
97
  "url": config.url,
50
98
  "logo": config.logo,
99
+ "description": config.description,
51
100
  "sameAs": config.socialLinks || []
52
- };
101
+ });
53
102
  }
54
103
  function generateWebSiteSchema(config) {
55
- return {
104
+ return cleanSchema({
56
105
  "@context": "https://schema.org",
57
106
  "@type": "WebSite",
58
107
  "name": config.name,
59
108
  "url": config.url,
109
+ "description": config.description,
110
+ "publisher": {
111
+ "@type": "Organization",
112
+ "name": config.name,
113
+ "logo": config.logo
114
+ },
60
115
  "potentialAction": {
61
116
  "@type": "SearchAction",
62
117
  "target": `${config.url}/search?q={search_term_string}`,
63
118
  "query-input": "required name=search_term_string"
64
119
  }
65
- };
120
+ });
121
+ }
122
+ function generateWebPageSchema(data, config) {
123
+ return cleanSchema({
124
+ "@context": "https://schema.org",
125
+ "@type": "WebPage",
126
+ "name": data.name,
127
+ "description": data.description,
128
+ "url": data.url,
129
+ "image": data.image,
130
+ "datePublished": data.datePublished,
131
+ "dateModified": data.dateModified,
132
+ "isPartOf": {
133
+ "@type": "WebSite",
134
+ "name": config.name,
135
+ "url": config.url
136
+ },
137
+ "breadcrumb": data.breadcrumb ? generateBreadcrumbSchema(data.breadcrumb) : void 0
138
+ });
139
+ }
140
+ function generateCollectionPageSchema(data, config) {
141
+ return cleanSchema({
142
+ "@context": "https://schema.org",
143
+ "@type": "CollectionPage",
144
+ "name": data.name,
145
+ "description": data.description,
146
+ "url": data.url,
147
+ "image": data.image,
148
+ "numberOfItems": data.numberOfItems,
149
+ "isPartOf": {
150
+ "@type": "WebSite",
151
+ "name": config.name,
152
+ "url": config.url
153
+ }
154
+ });
155
+ }
156
+ function generateItemListSchema(data) {
157
+ return cleanSchema({
158
+ "@context": "https://schema.org",
159
+ "@type": "ItemList",
160
+ "name": data.name,
161
+ "url": data.url,
162
+ "itemListOrder": data.itemListOrder === "ascending" ? "https://schema.org/ItemListOrderAscending" : data.itemListOrder === "descending" ? "https://schema.org/ItemListOrderDescending" : "https://schema.org/ItemListUnordered",
163
+ "numberOfItems": data.items.length,
164
+ "itemListElement": data.items.map((item, index) => ({
165
+ "@type": "ListItem",
166
+ "position": item.position || index + 1,
167
+ "name": item.name,
168
+ "url": item.url,
169
+ "image": item.image
170
+ }))
171
+ });
66
172
  }
67
173
  function generateArticleSchema(data, config) {
174
+ validateSEO("NewsArticle", data, ["title", "description", "image", "publishedTime", "author"]);
68
175
  const org = generateOrganizationSchema(config);
69
- return {
176
+ return cleanSchema({
70
177
  "@context": "https://schema.org",
71
178
  "@type": "NewsArticle",
72
179
  "headline": data.title,
73
180
  "description": data.description,
74
- "image": data.image || config.logo,
181
+ "image": data.image,
75
182
  "datePublished": data.publishedTime,
76
183
  "dateModified": data.modifiedTime || data.publishedTime,
77
184
  "mainEntityOfPage": data.url,
185
+ "wordCount": data.wordCount,
78
186
  "author": data.author ? {
79
187
  "@type": "Person",
80
188
  "name": data.author.name,
81
189
  "url": data.author.url
82
190
  } : org,
83
191
  "publisher": org
84
- };
192
+ });
85
193
  }
86
194
  function generateProductSchema(data) {
87
- return {
195
+ validateSEO("Product", data, ["name", "description", "image", "price"]);
196
+ let offers;
197
+ if (data.variants && data.variants.length > 0) {
198
+ const prices = data.variants.map((v) => v.price);
199
+ offers = {
200
+ "@type": "AggregateOffer",
201
+ "lowPrice": Math.min(...prices),
202
+ "highPrice": Math.max(...prices),
203
+ "priceCurrency": data.currency || data.variants[0]?.currency || "USD",
204
+ "offerCount": data.variants.length,
205
+ "offers": data.variants.map((v) => cleanSchema({
206
+ "@type": "Offer",
207
+ "name": v.name,
208
+ "sku": v.sku,
209
+ "price": v.price,
210
+ "priceCurrency": v.currency || data.currency || "USD",
211
+ "availability": v.availability || data.availability || "https://schema.org/InStock",
212
+ "url": v.url || data.url,
213
+ "image": v.image,
214
+ "itemCondition": data.condition ? `https://schema.org/${data.condition}` : void 0
215
+ }))
216
+ };
217
+ } else {
218
+ offers = cleanSchema({
219
+ "@type": "Offer",
220
+ "url": data.url,
221
+ "priceCurrency": data.currency || "USD",
222
+ "price": data.price,
223
+ "availability": data.availability || "https://schema.org/InStock",
224
+ "itemCondition": data.condition ? `https://schema.org/${data.condition}` : void 0,
225
+ "seller": data.seller ? {
226
+ "@type": "Organization",
227
+ "name": data.seller.name,
228
+ "url": data.seller.url
229
+ } : void 0,
230
+ "hasMerchantReturnPolicy": data.returnPolicy ? cleanSchema({
231
+ "@type": "MerchantReturnPolicy",
232
+ "applicableCountry": data.shipping?.shippingDestination,
233
+ "returnPolicyCategory": data.returnPolicy.returnPolicyCategory ? `https://schema.org/${data.returnPolicy.returnPolicyCategory}` : "https://schema.org/MerchantReturnFiniteReturnWindow",
234
+ "merchantReturnDays": data.returnPolicy.returnWithin,
235
+ "returnMethod": data.returnPolicy.returnMethod ? `https://schema.org/${data.returnPolicy.returnMethod}` : void 0,
236
+ "returnFees": data.returnPolicy.returnFees ? `https://schema.org/${data.returnPolicy.returnFees}` : void 0
237
+ }) : void 0,
238
+ "shippingDetails": data.shipping ? cleanSchema({
239
+ "@type": "OfferShippingDetails",
240
+ "shippingRate": data.shipping.shippingRate ? {
241
+ "@type": "MonetaryAmount",
242
+ "value": data.shipping.shippingRate.value,
243
+ "currency": data.shipping.shippingRate.currency
244
+ } : void 0,
245
+ "shippingDestination": data.shipping.shippingDestination ? {
246
+ "@type": "DefinedRegion",
247
+ "addressCountry": data.shipping.shippingDestination
248
+ } : void 0,
249
+ "deliveryTime": data.shipping.deliveryTime ? {
250
+ "@type": "ShippingDeliveryTime",
251
+ "handlingTime": {
252
+ "@type": "QuantitativeValue",
253
+ "minValue": 0,
254
+ "maxValue": 1,
255
+ "unitCode": "DAY"
256
+ },
257
+ "transitTime": {
258
+ "@type": "QuantitativeValue",
259
+ "minValue": data.shipping.deliveryTime.minDays,
260
+ "maxValue": data.shipping.deliveryTime.maxDays,
261
+ "unitCode": "DAY"
262
+ }
263
+ } : void 0,
264
+ "freeShippingThreshold": data.shipping.freeShippingThreshold ? {
265
+ "@type": "MonetaryAmount",
266
+ "value": data.shipping.freeShippingThreshold,
267
+ "currency": data.shipping.shippingRate?.currency || data.currency || "USD"
268
+ } : void 0
269
+ }) : void 0
270
+ });
271
+ }
272
+ const reviewList = data.reviews?.map((r) => cleanSchema({
273
+ "@type": "Review",
274
+ "author": { "@type": "Person", "name": r.author },
275
+ "datePublished": r.datePublished,
276
+ "reviewBody": r.reviewBody,
277
+ "reviewRating": {
278
+ "@type": "Rating",
279
+ "ratingValue": r.ratingValue,
280
+ "bestRating": r.bestRating || 5
281
+ }
282
+ }));
283
+ return cleanSchema({
88
284
  "@context": "https://schema.org",
89
285
  "@type": "Product",
90
286
  "name": data.name,
91
287
  "description": data.description,
92
288
  "image": data.image,
93
289
  "sku": data.sku,
290
+ "gtin": data.gtin,
291
+ "mpn": data.mpn,
94
292
  "brand": data.brand ? { "@type": "Brand", "name": data.brand } : void 0,
95
- "offers": {
96
- "@type": "Offer",
97
- "url": data.url,
98
- "priceCurrency": data.currency || "USD",
99
- "price": data.price,
100
- "availability": data.availability || "https://schema.org/InStock"
101
- },
293
+ "offers": offers,
102
294
  "aggregateRating": data.rating ? {
103
295
  "@type": "AggregateRating",
104
296
  "ratingValue": data.rating,
105
297
  "reviewCount": data.reviewCount || 1
106
- } : void 0
107
- };
298
+ } : void 0,
299
+ "review": reviewList && reviewList.length > 0 ? reviewList : void 0
300
+ });
108
301
  }
109
302
  function generateFAQSchema(questions) {
110
- return {
303
+ return cleanSchema({
111
304
  "@context": "https://schema.org",
112
305
  "@type": "FAQPage",
113
306
  "mainEntity": questions.map((item) => ({
@@ -118,10 +311,10 @@ function generateFAQSchema(questions) {
118
311
  "text": item.a
119
312
  }
120
313
  }))
121
- };
314
+ });
122
315
  }
123
316
  function generateBreadcrumbSchema(items) {
124
- return {
317
+ return cleanSchema({
125
318
  "@context": "https://schema.org",
126
319
  "@type": "BreadcrumbList",
127
320
  "itemListElement": items.map((item, index) => ({
@@ -130,10 +323,11 @@ function generateBreadcrumbSchema(items) {
130
323
  "name": item.name,
131
324
  "item": item.item
132
325
  }))
133
- };
326
+ });
134
327
  }
135
328
  function generateVideoSchema(data) {
136
- return {
329
+ validateSEO("VideoObject", data, ["name", "description", "thumbnailUrl", "uploadDate"]);
330
+ return cleanSchema({
137
331
  "@context": "https://schema.org",
138
332
  "@type": "VideoObject",
139
333
  "name": data.name,
@@ -143,11 +337,11 @@ function generateVideoSchema(data) {
143
337
  "duration": data.duration,
144
338
  "contentUrl": data.contentUrl,
145
339
  "embedUrl": data.embedUrl
146
- };
340
+ });
147
341
  }
148
342
  function generateEventSchema(data) {
149
343
  const isOnline = data.location && "url" in data.location;
150
- return {
344
+ return cleanSchema({
151
345
  "@context": "https://schema.org",
152
346
  "@type": "Event",
153
347
  "name": data.name,
@@ -170,10 +364,10 @@ function generateEventSchema(data) {
170
364
  "priceCurrency": data.offers.currency,
171
365
  "url": data.offers.url
172
366
  } : void 0
173
- };
367
+ });
174
368
  }
175
369
  function generateLocalBusinessSchema(data) {
176
- return {
370
+ return cleanSchema({
177
371
  "@context": "https://schema.org",
178
372
  "@type": "LocalBusiness",
179
373
  "name": data.name,
@@ -195,10 +389,10 @@ function generateLocalBusinessSchema(data) {
195
389
  } : void 0,
196
390
  "openingHours": data.openingHours,
197
391
  "priceRange": data.priceRange
198
- };
392
+ });
199
393
  }
200
394
  function generateSoftwareSchema(data) {
201
- return {
395
+ return cleanSchema({
202
396
  "@context": "https://schema.org",
203
397
  "@type": "SoftwareApplication",
204
398
  "name": data.name,
@@ -217,10 +411,10 @@ function generateSoftwareSchema(data) {
217
411
  } : void 0,
218
412
  "downloadUrl": data.downloadUrl,
219
413
  "screenshot": data.screenshot
220
- };
414
+ });
221
415
  }
222
416
  function generateBookSchema(data) {
223
- return {
417
+ return cleanSchema({
224
418
  "@context": "https://schema.org",
225
419
  "@type": "Book",
226
420
  "name": data.name,
@@ -243,10 +437,10 @@ function generateBookSchema(data) {
243
437
  "image": data.image,
244
438
  "inLanguage": data.inLanguage,
245
439
  "genre": data.genre
246
- };
440
+ });
247
441
  }
248
442
  function generateMovieSchema(data) {
249
- return {
443
+ return cleanSchema({
250
444
  "@context": "https://schema.org",
251
445
  "@type": "Movie",
252
446
  "name": data.name,
@@ -268,10 +462,10 @@ function generateMovieSchema(data) {
268
462
  "ratingValue": data.rating,
269
463
  "reviewCount": data.reviewCount || 1
270
464
  } : void 0
271
- };
465
+ });
272
466
  }
273
467
  function generatePodcastSchema(data) {
274
- return {
468
+ return cleanSchema({
275
469
  "@context": "https://schema.org",
276
470
  "@type": "PodcastSeries",
277
471
  "name": data.name,
@@ -284,10 +478,10 @@ function generatePodcastSchema(data) {
284
478
  "webFeed": data.webFeed,
285
479
  "url": data.url,
286
480
  "genre": data.genre
287
- };
481
+ });
288
482
  }
289
483
  function generatePodcastEpisodeSchema(data) {
290
- return {
484
+ return cleanSchema({
291
485
  "@context": "https://schema.org",
292
486
  "@type": "PodcastEpisode",
293
487
  "name": data.name,
@@ -304,7 +498,109 @@ function generatePodcastEpisodeSchema(data) {
304
498
  "name": data.partOfSeries.name,
305
499
  "url": data.partOfSeries.url
306
500
  } : void 0
307
- };
501
+ });
502
+ }
503
+ function generateHowToSchema(data) {
504
+ return cleanSchema({
505
+ "@context": "https://schema.org",
506
+ "@type": "HowTo",
507
+ "name": data.name,
508
+ "description": data.description,
509
+ "image": data.image,
510
+ "totalTime": data.totalTime,
511
+ "estimatedCost": data.estimatedCost ? {
512
+ "@type": "MonetaryAmount",
513
+ "currency": data.estimatedCost.currency,
514
+ "value": data.estimatedCost.value
515
+ } : void 0,
516
+ "supply": data.supply?.map((s) => ({ "@type": "HowToSupply", "name": s })),
517
+ "tool": data.tool?.map((t) => ({ "@type": "HowToTool", "name": t })),
518
+ "step": data.steps.map((step, index) => {
519
+ if (typeof step === "string") {
520
+ return { "@type": "HowToStep", "position": index + 1, "text": step };
521
+ }
522
+ return cleanSchema({
523
+ "@type": "HowToStep",
524
+ "position": index + 1,
525
+ "name": step.name,
526
+ "text": step.text,
527
+ "image": step.image,
528
+ "url": step.url
529
+ });
530
+ })
531
+ });
532
+ }
533
+ function generateRecipeSchema(data) {
534
+ return cleanSchema({
535
+ "@context": "https://schema.org",
536
+ "@type": "Recipe",
537
+ "name": data.name,
538
+ "description": data.description,
539
+ "image": data.image,
540
+ "author": { "@type": "Person", "name": data.author },
541
+ "datePublished": data.publishedDate,
542
+ "prepTime": data.prepTime,
543
+ "cookTime": data.cookTime,
544
+ "totalTime": data.totalTime,
545
+ "recipeYield": data.recipeYield,
546
+ "recipeCategory": data.recipeCategory,
547
+ "recipeCuisine": data.recipeCuisine,
548
+ "recipeIngredient": data.ingredients,
549
+ "recipeInstructions": data.instructions.map((step) => cleanSchema({
550
+ "@type": "HowToStep",
551
+ "name": step.name,
552
+ "text": step.text,
553
+ "image": step.image
554
+ })),
555
+ "aggregateRating": data.rating ? {
556
+ "@type": "AggregateRating",
557
+ "ratingValue": data.rating,
558
+ "reviewCount": data.reviewCount || 1
559
+ } : void 0
560
+ });
561
+ }
562
+ function generateJobPostingSchema(data) {
563
+ return cleanSchema({
564
+ "@context": "https://schema.org",
565
+ "@type": "JobPosting",
566
+ "title": data.title,
567
+ "description": data.description,
568
+ "datePosted": data.datePosted,
569
+ "validThrough": data.validThrough,
570
+ "employmentType": data.employmentType,
571
+ "jobLocationType": data.remote ? "TELECOMMUTE" : void 0,
572
+ "hiringOrganization": {
573
+ "@type": "Organization",
574
+ "name": data.hiringOrganization.name,
575
+ "sameAs": data.hiringOrganization.sameAs,
576
+ "logo": data.hiringOrganization.logo
577
+ },
578
+ "jobLocation": {
579
+ "@type": "Place",
580
+ "address": {
581
+ "@type": "PostalAddress",
582
+ "streetAddress": data.jobLocation.streetAddress,
583
+ "addressLocality": data.jobLocation.addressLocality,
584
+ "addressRegion": data.jobLocation.addressRegion,
585
+ "postalCode": data.jobLocation.postalCode,
586
+ "addressCountry": data.jobLocation.addressCountry
587
+ }
588
+ },
589
+ "baseSalary": data.baseSalary ? {
590
+ "@type": "MonetaryAmount",
591
+ "currency": data.baseSalary.currency,
592
+ "value": typeof data.baseSalary.value === "number" ? {
593
+ "@type": "QuantitativeValue",
594
+ "value": data.baseSalary.value,
595
+ "unitText": data.baseSalary.unitText || "MONTH"
596
+ } : {
597
+ "@type": "QuantitativeValue",
598
+ "minValue": data.baseSalary.value.minValue,
599
+ "maxValue": data.baseSalary.value.maxValue,
600
+ "unitText": data.baseSalary.unitText || "MONTH"
601
+ }
602
+ } : void 0
603
+ });
308
604
  }
309
605
 
310
606
  // src/core/metadata.ts
@@ -348,6 +644,9 @@ function toNextMetadata(props, config) {
348
644
  },
349
645
  other: {}
350
646
  };
647
+ if (config.facebookAppId) {
648
+ metadata.other["fb:app_id"] = config.facebookAppId;
649
+ }
351
650
  if (props.alternates && props.alternates.length > 0) {
352
651
  const languages = {};
353
652
  props.alternates.forEach((alt) => {
@@ -355,6 +654,12 @@ function toNextMetadata(props, config) {
355
654
  });
356
655
  metadata.alternates.languages = languages;
357
656
  }
657
+ if (props.prev) {
658
+ metadata.alternates.prev = props.prev;
659
+ }
660
+ if (props.next) {
661
+ metadata.alternates.next = props.next;
662
+ }
358
663
  metadata.appleWebApp = {
359
664
  capable: true,
360
665
  title: config.name,
@@ -362,6 +667,45 @@ function toNextMetadata(props, config) {
362
667
  };
363
668
  if (config.themeColor) metadata.themeColor = config.themeColor;
364
669
  if (config.manifest) metadata.manifest = config.manifest;
670
+ if (props.type === "article") {
671
+ if (props.publishedTime) {
672
+ metadata.openGraph.publishedTime = props.publishedTime;
673
+ }
674
+ if (props.modifiedTime) {
675
+ metadata.openGraph.modifiedTime = props.modifiedTime;
676
+ }
677
+ if (props.author) {
678
+ metadata.openGraph.authors = [props.author.name];
679
+ }
680
+ if (props.section) {
681
+ metadata.openGraph.section = props.section;
682
+ }
683
+ if (props.tags?.length) {
684
+ metadata.openGraph.tags = props.tags;
685
+ }
686
+ }
687
+ if (props.type === "product" && props.product) {
688
+ if (props.product.price !== void 0 && props.product.currency) {
689
+ metadata.other["product:price:amount"] = props.product.price.toString();
690
+ metadata.other["product:price:currency"] = props.product.currency;
691
+ }
692
+ if (props.product.availability) {
693
+ metadata.other["product:availability"] = props.product.availability;
694
+ }
695
+ if (props.product.brand) {
696
+ metadata.other["product:brand"] = props.product.brand;
697
+ }
698
+ }
699
+ if (props.readingTime) {
700
+ metadata.other["twitter:label1"] = "Reading time";
701
+ metadata.other["twitter:data1"] = `${props.readingTime} min`;
702
+ }
703
+ if (props.whatsappImage) {
704
+ metadata.other["og:image:secure_url"] = props.whatsappImage;
705
+ }
706
+ if (Object.keys(metadata.other).length === 0) {
707
+ delete metadata.other;
708
+ }
365
709
  return metadata;
366
710
  }
367
711
  function generatePaginationLinks(baseUrl, currentPage, totalPages) {
@@ -374,16 +718,312 @@ function generatePaginationLinks(baseUrl, currentPage, totalPages) {
374
718
  canonical: currentPage === 1 ? cleanBase : `${cleanBase}?page=${currentPage}`
375
719
  };
376
720
  }
377
- function generatePaginatedTitle(title, page, suffix = "\u0635\u0641\u062D\u0629") {
721
+ function generatePaginatedTitle(title, page, suffix = "Page") {
378
722
  return page > 1 ? `${title} - ${suffix} ${page}` : title;
379
723
  }
724
+
725
+ // src/core/JsonLd.tsx
726
+ var import_jsx_runtime = require("react/jsx-runtime");
727
+ function JsonLd({ schema, graph = false }) {
728
+ const schemas = Array.isArray(schema) ? schema : [schema];
729
+ if (graph && schemas.length > 1) {
730
+ const graphData = {
731
+ "@context": "https://schema.org",
732
+ "@graph": schemas.map((s) => {
733
+ const { "@context": _, ...rest } = s;
734
+ return rest;
735
+ })
736
+ };
737
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
738
+ "script",
739
+ {
740
+ type: "application/ld+json",
741
+ dangerouslySetInnerHTML: { __html: JSON.stringify(graphData) }
742
+ }
743
+ );
744
+ }
745
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: schemas.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
746
+ "script",
747
+ {
748
+ type: "application/ld+json",
749
+ dangerouslySetInnerHTML: { __html: JSON.stringify(s) }
750
+ },
751
+ i
752
+ )) });
753
+ }
754
+
755
+ // src/core/product-metadata.ts
756
+ function generateProductMetadata(product, config) {
757
+ const primaryImage = Array.isArray(product.image) ? product.image[0] : product.image;
758
+ const seoData = {
759
+ title: product.metaTitle || product.name,
760
+ description: product.metaDescription || product.description,
761
+ image: product.ogImage || primaryImage,
762
+ canonical: product.canonical || product.url,
763
+ type: "product",
764
+ noindex: product.noindex,
765
+ ogTitle: product.name,
766
+ ogDescription: product.description,
767
+ ogImage: product.ogImage || primaryImage,
768
+ ogImageWidth: product.ogImageWidth || 1200,
769
+ ogImageHeight: product.ogImageHeight || 630,
770
+ ogType: "product",
771
+ product: {
772
+ sku: product.sku,
773
+ brand: product.brand,
774
+ price: product.price,
775
+ currency: product.currency,
776
+ availability: product.availability,
777
+ rating: product.rating,
778
+ reviewCount: product.reviewCount
779
+ }
780
+ };
781
+ const metadata = toNextMetadata(seoData, config);
782
+ if (product.price !== void 0 && product.currency) {
783
+ metadata.other = {
784
+ ...metadata.other,
785
+ "product:price:amount": product.price.toString(),
786
+ "product:price:currency": product.currency
787
+ };
788
+ if (product.availability) {
789
+ metadata.other["product:availability"] = product.availability;
790
+ }
791
+ if (product.brand) {
792
+ metadata.other["product:brand"] = product.brand;
793
+ }
794
+ if (product.condition) {
795
+ metadata.other["product:condition"] = product.condition;
796
+ }
797
+ }
798
+ const productSchema = generateProductSchema({
799
+ name: product.name,
800
+ description: product.description,
801
+ image: product.image,
802
+ sku: product.sku,
803
+ gtin: product.gtin,
804
+ mpn: product.mpn,
805
+ brand: product.brand,
806
+ price: product.price,
807
+ currency: product.currency,
808
+ availability: product.availability,
809
+ rating: product.rating,
810
+ reviewCount: product.reviewCount,
811
+ url: product.url,
812
+ condition: product.condition,
813
+ reviews: product.reviews,
814
+ returnPolicy: product.returnPolicy,
815
+ shipping: product.shipping,
816
+ variants: product.variants,
817
+ seller: product.seller
818
+ });
819
+ const breadcrumbItems = product.breadcrumbs || [
820
+ { name: "Home", item: config.url },
821
+ { name: "Shop", item: `${config.url}/shop` },
822
+ ...product.category ? [{ name: product.category, item: `${config.url}/categories/${encodeURIComponent(product.category)}` }] : [],
823
+ { name: product.name, item: product.url }
824
+ ];
825
+ const breadcrumbSchema = generateBreadcrumbSchema(breadcrumbItems);
826
+ const organizationSchema = generateOrganizationSchema(config);
827
+ const websiteSchema = generateWebSiteSchema(config);
828
+ return {
829
+ metadata,
830
+ schemas: [productSchema, breadcrumbSchema, organizationSchema, websiteSchema],
831
+ productSchema,
832
+ breadcrumbSchema,
833
+ organizationSchema,
834
+ websiteSchema
835
+ };
836
+ }
837
+
838
+ // src/core/article-metadata.ts
839
+ function generateArticleMetadata(article, config) {
840
+ const primaryImage = Array.isArray(article.image) ? article.image[0] : article.image;
841
+ const seoData = {
842
+ title: article.metaTitle || article.title,
843
+ description: article.metaDescription || article.description,
844
+ image: article.ogImage || primaryImage,
845
+ canonical: article.canonical || article.url,
846
+ type: "article",
847
+ noindex: article.noindex,
848
+ publishedTime: article.publishedTime,
849
+ modifiedTime: article.modifiedTime,
850
+ author: article.author,
851
+ section: article.category,
852
+ tags: article.tags,
853
+ readingTime: article.readingTime,
854
+ ogTitle: article.title,
855
+ ogDescription: article.description,
856
+ ogImage: article.ogImage || primaryImage,
857
+ ogImageWidth: article.ogImageWidth || 1200,
858
+ ogImageHeight: article.ogImageHeight || 630,
859
+ ogType: "article"
860
+ };
861
+ const metadata = toNextMetadata(seoData, config);
862
+ if (article.publishedTime) {
863
+ metadata.other = {
864
+ ...metadata.other,
865
+ "article:published_time": article.publishedTime
866
+ };
867
+ }
868
+ if (article.modifiedTime) {
869
+ metadata.other = {
870
+ ...metadata.other,
871
+ "article:modified_time": article.modifiedTime
872
+ };
873
+ }
874
+ if (article.category) {
875
+ metadata.other = {
876
+ ...metadata.other,
877
+ "article:section": article.category
878
+ };
879
+ }
880
+ if (article.tags?.length) {
881
+ metadata.other = {
882
+ ...metadata.other,
883
+ "article:tag": article.tags.join(",")
884
+ };
885
+ }
886
+ const articleSchema = generateArticleSchema({
887
+ title: article.title,
888
+ description: article.description,
889
+ image: article.image,
890
+ publishedTime: article.publishedTime,
891
+ modifiedTime: article.modifiedTime,
892
+ author: article.author,
893
+ url: article.url,
894
+ wordCount: article.wordCount
895
+ }, config);
896
+ const breadcrumbItems = article.breadcrumbs || [
897
+ { name: "Home", item: config.url },
898
+ ...article.category ? [{ name: article.category, item: `${config.url}/category/${encodeURIComponent(article.category)}` }] : [],
899
+ { name: article.title, item: article.url }
900
+ ];
901
+ const breadcrumbSchema = generateBreadcrumbSchema(breadcrumbItems);
902
+ const organizationSchema = generateOrganizationSchema(config);
903
+ const websiteSchema = generateWebSiteSchema(config);
904
+ return {
905
+ metadata,
906
+ schemas: [articleSchema, breadcrumbSchema, organizationSchema, websiteSchema],
907
+ articleSchema,
908
+ breadcrumbSchema,
909
+ organizationSchema,
910
+ websiteSchema
911
+ };
912
+ }
913
+
914
+ // src/core/category-metadata.ts
915
+ function generateCategoryMetadata(category, config) {
916
+ const page = category.page || 1;
917
+ const totalPages = category.totalPages || 1;
918
+ const pageSuffix = category.pageSuffix || "Page";
919
+ const pagination = generatePaginationLinks(category.url, page, totalPages);
920
+ const title = generatePaginatedTitle(
921
+ category.metaTitle || category.name,
922
+ page,
923
+ pageSuffix
924
+ );
925
+ const seoData = {
926
+ title,
927
+ description: category.metaDescription || category.description,
928
+ image: category.image,
929
+ canonical: pagination.canonical,
930
+ type: "website",
931
+ noindex: category.noindex,
932
+ prev: pagination.prev,
933
+ next: pagination.next
934
+ };
935
+ const metadata = toNextMetadata(seoData, config);
936
+ const collectionPageSchema = generateCollectionPageSchema({
937
+ name: category.name,
938
+ description: category.description,
939
+ url: pagination.canonical || category.url,
940
+ image: category.image,
941
+ numberOfItems: category.items?.length
942
+ }, config);
943
+ const breadcrumbItems = category.breadcrumbs || [
944
+ { name: "Home", item: config.url },
945
+ ...category.parentCategory ? [{ name: category.parentCategory, item: `${config.url}/categories` }] : [],
946
+ { name: category.name, item: category.url }
947
+ ];
948
+ const breadcrumbSchema = generateBreadcrumbSchema(breadcrumbItems);
949
+ const organizationSchema = generateOrganizationSchema(config);
950
+ let itemListSchema;
951
+ if (category.items && category.items.length > 0) {
952
+ itemListSchema = generateItemListSchema({
953
+ name: category.name,
954
+ url: pagination.canonical || category.url,
955
+ items: category.items
956
+ });
957
+ }
958
+ const schemas = [collectionPageSchema, breadcrumbSchema, organizationSchema];
959
+ if (itemListSchema) schemas.push(itemListSchema);
960
+ return {
961
+ metadata,
962
+ schemas,
963
+ collectionPageSchema,
964
+ breadcrumbSchema,
965
+ organizationSchema,
966
+ itemListSchema
967
+ };
968
+ }
969
+
970
+ // src/core/homepage-metadata.ts
971
+ function generateHomepageMetadata(input, config) {
972
+ const seoData = {
973
+ title: input.title || config.name,
974
+ description: input.description || config.description,
975
+ image: input.ogImage || input.image || config.logo,
976
+ canonical: input.canonical || config.url,
977
+ type: "website",
978
+ ogTitle: input.title || config.name,
979
+ ogDescription: input.description || config.description,
980
+ ogImage: input.ogImage || input.image || config.logo,
981
+ ogImageWidth: input.ogImageWidth || 1200,
982
+ ogImageHeight: input.ogImageHeight || 630
983
+ };
984
+ const metadata = toNextMetadata(seoData, config);
985
+ const webPageSchema = generateWebPageSchema({
986
+ name: input.title || config.name,
987
+ description: input.description || config.description,
988
+ url: config.url,
989
+ image: input.image || config.logo
990
+ }, config);
991
+ const organizationSchema = generateOrganizationSchema({
992
+ ...config,
993
+ socialLinks: input.socialLinks || config.socialLinks
994
+ });
995
+ const websiteSchema = generateWebSiteSchema(config);
996
+ const schemas = [webPageSchema, organizationSchema, websiteSchema];
997
+ let localBusinessSchema;
998
+ if (input.localBusiness) {
999
+ localBusinessSchema = generateLocalBusinessSchema(input.localBusiness);
1000
+ schemas.push(localBusinessSchema);
1001
+ }
1002
+ return {
1003
+ metadata,
1004
+ schemas,
1005
+ webPageSchema,
1006
+ organizationSchema,
1007
+ websiteSchema,
1008
+ localBusinessSchema
1009
+ };
1010
+ }
380
1011
  // Annotate the CommonJS export names for ESM import in node:
381
1012
  0 && (module.exports = {
1013
+ JsonLd,
1014
+ cleanSchema,
1015
+ generateArticleMetadata,
382
1016
  generateArticleSchema,
383
1017
  generateBookSchema,
384
1018
  generateBreadcrumbSchema,
1019
+ generateCategoryMetadata,
1020
+ generateCollectionPageSchema,
385
1021
  generateEventSchema,
386
1022
  generateFAQSchema,
1023
+ generateHomepageMetadata,
1024
+ generateHowToSchema,
1025
+ generateItemListSchema,
1026
+ generateJobPostingSchema,
387
1027
  generateLocalBusinessSchema,
388
1028
  generateMovieSchema,
389
1029
  generateOrganizationSchema,
@@ -391,9 +1031,13 @@ function generatePaginatedTitle(title, page, suffix = "\u0635\u0641\u062D\u0629"
391
1031
  generatePaginationLinks,
392
1032
  generatePodcastEpisodeSchema,
393
1033
  generatePodcastSchema,
1034
+ generateProductMetadata,
394
1035
  generateProductSchema,
1036
+ generateRecipeSchema,
395
1037
  generateSoftwareSchema,
396
1038
  generateVideoSchema,
1039
+ generateWebPageSchema,
397
1040
  generateWebSiteSchema,
398
- toNextMetadata
1041
+ toNextMetadata,
1042
+ validateSEO
399
1043
  });