@masters-ws/react-seo 1.4.4 → 1.5.1

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.
package/README.md CHANGED
@@ -1,1482 +1,308 @@
1
- [![Total Downloads](https://img.shields.io/npm/dt/@masters-ws/react-seo.svg?style=flat-square)](https://www.npmjs.com/package/@masters-ws/react-seo)
2
- # @masters-ws/react-seo
3
-
4
- Professional high-performance SEO package for React and Next.js. **Zero-dependency core** for Next.js App Router + optional Helmet components for universal React support.
5
-
6
- ## Key Features
7
-
8
- - ✅ **Zero-Dependency Core** — Pure functions for Next.js (no react-helmet-async needed!)
9
- - ✅ **SSR-First Architecture** — All metadata rendered server-side for Google crawlers
10
- - ✅ **Clean JSON-LD** — Automatic removal of `undefined`/`null` values from schemas
11
- - **@graph Support** — Combine multiple schemas into a single `<script>` tag (Google recommended)
12
- - ✅ **Full Metadata Support** — Title, Description, OG, Twitter Cards, Canonical, Robots
13
- - **Intelligent Pagination**Automatic `rel="prev"` and `rel="next"` handling
14
- - ✅ **30+ Schema Types** — Product, Article, FAQ, Event, LocalBusiness, Video, Recipe, etc.
15
- - **Rich Product Schema** — Multi-image, reviews, return policy, shipping, variants
16
- - ✅ **Convenience Helpers** — One-call setup: Product, Article, Category pages
17
- - **Development Warnings** Console warnings for missing required schema fields
18
- - **i18n Ready** No hardcoded strings, all labels are configurable
19
- - ✅ **Multilingual Support** Easy `hreflang` management
20
- - **Performance Optimized** DNS Prefetch, Preconnect, Preload support
21
-
22
- ## Installation
23
-
24
- ### For Next.js App Router (Zero Dependencies):
25
- ```bash
26
- npm install @masters-ws/react-seo
27
- ```
28
-
29
- ### For React / Next.js Pages Router (With Components):
30
- ```bash
31
- npm install @masters-ws/react-seo react-helmet-async
32
- ```
33
-
34
- ---
35
-
36
- ## 🚀 Quick Start: Next.js App Router (Recommended)
37
-
38
- ### Product Page (One-Call Setup)
39
-
40
- ```tsx
41
- // app/products/[slug]/page.tsx — Server Component (NO 'use client')
42
- import { generateProductMetadata, JsonLd } from '@masters-ws/react-seo/core';
43
-
44
- const siteConfig = {
45
- name: "My Store",
46
- url: "https://store.com",
47
- logo: "https://store.com/logo.png",
48
- description: "Best online store",
49
- language: "en_US",
50
- };
51
-
52
- // Server-side metadata Google sees it immediately!
53
- export async function generateMetadata({ params }: { params: { slug: string } }) {
54
- const product = await fetchProduct(params.slug);
55
- const { metadata } = generateProductMetadata({
56
- name: product.name,
57
- description: product.short_description,
58
- image: [product.main_image, ...product.gallery], // Multi-image support
59
- price: product.price,
60
- currency: "USD",
61
- sku: product.sku,
62
- brand: product.brand?.name,
63
- availability: product.in_stock
64
- ? "https://schema.org/InStock"
65
- : "https://schema.org/OutOfStock",
66
- url: `https://store.com/products/${params.slug}`,
67
- metaTitle: product.meta_title,
68
- metaDescription: product.meta_description,
69
-
70
- // Individual reviews (optional)
71
- reviews: product.reviews?.map(r => ({
72
- author: r.user.name,
73
- ratingValue: r.rating,
74
- reviewBody: r.comment,
75
- datePublished: r.created_at,
76
- })),
77
-
78
- // Return policy (optional)
79
- returnPolicy: {
80
- returnPolicyCategory: 'MerchantReturnFiniteReturnWindow',
81
- returnWithin: 30,
82
- returnMethod: 'ReturnByMail',
83
- returnFees: 'FreeReturn',
84
- },
85
-
86
- // Shipping (optional)
87
- shipping: {
88
- shippingRate: { value: 5.99, currency: "USD" },
89
- shippingDestination: "US",
90
- deliveryTime: { minDays: 3, maxDays: 7 },
91
- },
92
-
93
- // Product variants (optional — generates AggregateOffer)
94
- variants: product.variants?.map(v => ({
95
- name: v.name,
96
- sku: v.sku,
97
- price: v.price,
98
- })),
99
-
100
- breadcrumbs: [
101
- { name: "Home", item: "https://store.com" },
102
- { name: "Shop", item: "https://store.com/shop" },
103
- { name: product.category?.name, item: `https://store.com/categories/${product.category?.slug}` },
104
- { name: product.name, item: `https://store.com/products/${params.slug}` },
105
- ],
106
- }, siteConfig);
107
-
108
- return metadata;
109
- }
110
-
111
- // ✅ Server-rendered JSON-LD schemas with @graph pattern
112
- export default async function ProductPage({ params }: { params: { slug: string } }) {
113
- const product = await fetchProduct(params.slug);
114
- const { schemas } = generateProductMetadata({ /* same data */ }, siteConfig);
115
-
116
- return (
117
- <>
118
- <JsonLd schema={schemas} graph />
119
- <ProductDetailClient product={product} />
120
- </>
121
- );
122
- }
123
- ```
124
-
125
- **What Google sees in the HTML source:**
126
- ```html
127
- <!-- ✅ Server-rendered <head> tags -->
128
- <title>Product Name | My Store</title>
129
- <meta name="description" content="Product description..." />
130
- <meta property="og:title" content="Product Name" />
131
- <meta property="og:image" content="https://store.com/product.jpg" />
132
- <meta property="og:type" content="product" />
133
- <meta name="twitter:card" content="summary_large_image" />
134
- <link rel="canonical" href="https://store.com/products/my-product" />
135
-
136
- <!-- ✅ Server-rendered JSON-LD with @graph -->
137
- <script type="application/ld+json">
138
- {
139
- "@context": "https://schema.org",
140
- "@graph": [
141
- { "@type": "Product", "name": "...", "offers": {...}, "review": [...] },
142
- { "@type": "BreadcrumbList", "itemListElement": [...] },
143
- { "@type": "Organization", "name": "...", "logo": "..." },
144
- { "@type": "WebSite", "name": "...", "potentialAction": {...} }
145
- ]
146
- }
147
- </script>
148
- ```
149
-
150
- ### Article Page (One-Call Setup)
151
-
152
- ```tsx
153
- // app/news/[slug]/page.tsx — Server Component
154
- import { generateArticleMetadata, JsonLd } from '@masters-ws/react-seo/core';
155
-
156
- export async function generateMetadata({ params }) {
157
- const article = await fetchArticle(params.slug);
158
- const { metadata } = generateArticleMetadata({
159
- title: article.title,
160
- description: article.excerpt,
161
- image: article.cover_image,
162
- publishedTime: article.published_at,
163
- modifiedTime: article.updated_at,
164
- author: { name: article.author.name, url: article.author.url },
165
- url: `https://mysite.com/news/${params.slug}`,
166
- category: article.category,
167
- tags: article.tags,
168
- readingTime: article.reading_time,
169
- wordCount: article.word_count,
170
- }, siteConfig);
171
-
172
- return metadata;
173
- }
174
-
175
- export default async function ArticlePage({ params }) {
176
- const article = await fetchArticle(params.slug);
177
- const { schemas } = generateArticleMetadata({ /* same data */ }, siteConfig);
178
-
179
- return (
180
- <>
181
- <JsonLd schema={schemas} graph />
182
- <article>...</article>
183
- </>
184
- );
185
- }
186
- ```
187
-
188
- ### Category Page with Pagination
189
-
190
- ```tsx
191
- // app/categories/[slug]/page.tsx — Server Component
192
- import { generateCategoryMetadata, JsonLd } from '@masters-ws/react-seo/core';
193
-
194
- export async function generateMetadata({ params, searchParams }) {
195
- const page = Number(searchParams.page) || 1;
196
- const category = await fetchCategory(params.slug);
197
- const { metadata } = generateCategoryMetadata({
198
- name: category.name,
199
- description: category.description,
200
- url: `https://store.com/categories/${params.slug}`,
201
- image: category.image,
202
- page,
203
- totalPages: category.totalPages,
204
- // Products on this page → generates ItemList schema
205
- items: category.products.map((p, i) => ({
206
- name: p.name,
207
- url: `https://store.com/products/${p.slug}`,
208
- image: p.image,
209
- position: i + 1,
210
- })),
211
- }, siteConfig);
212
-
213
- return metadata;
214
- }
215
-
216
- export default async function CategoryPage({ params, searchParams }) {
217
- const page = Number(searchParams.page) || 1;
218
- const category = await fetchCategory(params.slug);
219
- const { schemas } = generateCategoryMetadata({ /* same data */ }, siteConfig);
220
-
221
- return (
222
- <>
223
- <JsonLd schema={schemas} graph />
224
- <ProductGrid products={category.products} />
225
- </>
226
- );
227
- }
228
- ```
229
-
230
- ---
231
-
232
- ## 📰 Use Case: Blog / News Site
233
-
234
- A complete guide for setting up SEO on a blog or news website.
235
-
236
- ### Blog Homepage
237
-
238
- ```tsx
239
- // app/page.tsx — Server Component
240
- import { generateHomepageMetadata, JsonLd } from '@masters-ws/react-seo/core';
241
-
242
- const siteConfig = {
243
- name: "Tech Blog",
244
- url: "https://techblog.com",
245
- logo: "https://techblog.com/logo.png",
246
- description: "Latest technology news, tutorials, and insights",
247
- language: "en_US",
248
- twitterHandle: "@techblog",
249
- socialLinks: [
250
- "https://twitter.com/techblog",
251
- "https://facebook.com/techblog",
252
- "https://linkedin.com/company/techblog",
253
- ],
254
- };
255
-
256
- export async function generateMetadata() {
257
- const { metadata } = generateHomepageMetadata({
258
- title: "Tech Blog Latest Technology News & Tutorials",
259
- description: "Stay updated with the latest tech news, programming tutorials, and expert insights.",
260
- ogImage: "https://techblog.com/og-cover.jpg",
261
- }, siteConfig);
262
-
263
- return metadata;
264
- }
265
-
266
- export default function HomePage() {
267
- const { schemas } = generateHomepageMetadata({
268
- title: "Tech Blog — Latest Technology News & Tutorials",
269
- description: "Stay updated with the latest tech news, programming tutorials, and expert insights.",
270
- }, siteConfig);
271
-
272
- return (
273
- <>
274
- <JsonLd schema={schemas} graph />
275
- <main>
276
- <h1>Welcome to Tech Blog</h1>
277
- {/* Latest articles grid */}
278
- </main>
279
- </>
280
- );
281
- }
282
- ```
283
-
284
- ### Article / Blog Post Page
285
-
286
- ```tsx
287
- // app/blog/[slug]/page.tsx — Server Component
288
- import { generateArticleMetadata, JsonLd } from '@masters-ws/react-seo/core';
289
-
290
- export async function generateMetadata({ params }: { params: { slug: string } }) {
291
- const article = await fetchArticle(params.slug);
292
-
293
- const { metadata } = generateArticleMetadata({
294
- title: article.title,
295
- description: article.excerpt,
296
- image: article.cover_image,
297
- publishedTime: article.published_at, // ISO 8601
298
- modifiedTime: article.updated_at,
299
- author: {
300
- name: article.author.name,
301
- url: `https://techblog.com/authors/${article.author.slug}`,
302
- },
303
- url: `https://techblog.com/blog/${params.slug}`,
304
- category: article.category.name, // e.g. "JavaScript"
305
- tags: article.tags.map(t => t.name), // ["React", "Next.js", "SEO"]
306
- readingTime: article.reading_time, // e.g. 5 (minutes)
307
- wordCount: article.word_count,
308
-
309
- breadcrumbs: [
310
- { name: "Home", item: "https://techblog.com" },
311
- { name: article.category.name, item: `https://techblog.com/category/${article.category.slug}` },
312
- { name: article.title, item: `https://techblog.com/blog/${params.slug}` },
313
- ],
314
- }, siteConfig);
315
-
316
- return metadata;
317
- }
318
-
319
- export default async function ArticlePage({ params }: { params: { slug: string } }) {
320
- const article = await fetchArticle(params.slug);
321
- const { schemas } = generateArticleMetadata({ /* same data as above */ }, siteConfig);
322
-
323
- return (
324
- <>
325
- <JsonLd schema={schemas} graph />
326
- <article>
327
- <h1>{article.title}</h1>
328
- <p>By {article.author.name} · {article.reading_time} min read</p>
329
- <div dangerouslySetInnerHTML={{ __html: article.content }} />
330
- </article>
331
- </>
332
- );
333
- }
334
- ```
335
-
336
- **What Google sees:**
337
- ```html
338
- <title>How to Master SEO in Next.js | Tech Blog</title>
339
- <meta name="description" content="A comprehensive guide to..." />
340
- <meta property="og:type" content="article" />
341
- <meta property="article:published_time" content="2024-06-15T10:00:00Z" />
342
- <meta property="article:author" content="John Doe" />
343
- <meta property="article:section" content="JavaScript" />
344
- <meta property="article:tag" content="React,Next.js,SEO" />
345
- <script type="application/ld+json">
346
- {
347
- "@context": "https://schema.org",
348
- "@graph": [
349
- { "@type": "NewsArticle", "headline": "...", "author": { "@type": "Person", "name": "..." } },
350
- { "@type": "BreadcrumbList", "itemListElement": [...] },
351
- { "@type": "Organization", ... },
352
- { "@type": "WebSite", "potentialAction": { "@type": "SearchAction", ... } }
353
- ]
354
- }
355
- </script>
356
- ```
357
-
358
- ### Blog Category Page (with Pagination)
359
-
360
- ```tsx
361
- // app/category/[slug]/page.tsx — Server Component
362
- import { generateCategoryMetadata, JsonLd } from '@masters-ws/react-seo/core';
363
-
364
- export async function generateMetadata({ params, searchParams }) {
365
- const page = Number(searchParams.page) || 1;
366
- const category = await fetchCategory(params.slug);
367
-
368
- const { metadata } = generateCategoryMetadata({
369
- name: category.name,
370
- description: `All articles about ${category.name}`,
371
- url: `https://techblog.com/category/${params.slug}`,
372
- image: category.cover_image,
373
- page,
374
- totalPages: category.totalPages,
375
- // Articles on this page → generates ItemList schema
376
- items: category.articles.map((a, i) => ({
377
- name: a.title,
378
- url: `https://techblog.com/blog/${a.slug}`,
379
- image: a.cover_image,
380
- position: (page - 1) * 10 + i + 1,
381
- })),
382
- }, siteConfig);
383
-
384
- return metadata;
385
- }
386
-
387
- export default async function CategoryPage({ params, searchParams }) {
388
- const page = Number(searchParams.page) || 1;
389
- const category = await fetchCategory(params.slug);
390
- const { schemas } = generateCategoryMetadata({ /* same data */ }, siteConfig);
391
-
392
- return (
393
- <>
394
- <JsonLd schema={schemas} graph />
395
- <h1>{category.name}</h1>
396
- <ArticleGrid articles={category.articles} />
397
- <Pagination current={page} total={category.totalPages} />
398
- </>
399
- );
400
- }
401
- ```
402
-
403
- > **Pagination SEO is automatic!** The helper generates:
404
- > - `rel="canonical"` (without `?page=1` for page 1)
405
- > - `rel="prev"` / `rel="next"` links
406
- > - Page number in title (e.g. "JavaScript - Page 2")
407
-
408
- ### Author Page
409
-
410
- ```tsx
411
- // app/authors/[slug]/page.tsx — Server Component
412
- import {
413
- toNextMetadata,
414
- generateWebPageSchema,
415
- generateOrganizationSchema,
416
- JsonLd
417
- } from '@masters-ws/react-seo/core';
418
-
419
- export async function generateMetadata({ params }) {
420
- const author = await fetchAuthor(params.slug);
421
-
422
- return toNextMetadata({
423
- title: `${author.name} — Author at Tech Blog`,
424
- description: author.bio,
425
- image: author.avatar,
426
- type: 'profile',
427
- canonical: `https://techblog.com/authors/${params.slug}`,
428
- }, siteConfig);
429
- }
430
-
431
- export default async function AuthorPage({ params }) {
432
- const author = await fetchAuthor(params.slug);
433
-
434
- const schemas = [
435
- {
436
- "@context": "https://schema.org",
437
- "@type": "Person",
438
- "name": author.name,
439
- "description": author.bio,
440
- "image": author.avatar,
441
- "jobTitle": author.job_title,
442
- "url": `https://techblog.com/authors/${params.slug}`,
443
- "sameAs": author.social_links,
444
- },
445
- generateOrganizationSchema(siteConfig),
446
- ];
447
-
448
- return (
449
- <>
450
- <JsonLd schema={schemas} graph />
451
- <h1>{author.name}</h1>
452
- <p>{author.bio}</p>
453
- <ArticleGrid articles={author.articles} />
454
- </>
455
- );
456
- }
457
- ```
458
-
459
- ### Tag Page
460
-
461
- ```tsx
462
- // app/tag/[slug]/page.tsx — Server Component
463
- import { generateCategoryMetadata, JsonLd } from '@masters-ws/react-seo/core';
464
-
465
- export async function generateMetadata({ params, searchParams }) {
466
- const page = Number(searchParams.page) || 1;
467
- const tag = await fetchTag(params.slug);
468
-
469
- // Reuse generateCategoryMetadata — tags are just another type of collection!
470
- const { metadata } = generateCategoryMetadata({
471
- name: `Tag: ${tag.name}`,
472
- description: tag.description || `All articles tagged with "${tag.name}"`,
473
- url: `https://techblog.com/tag/${params.slug}`,
474
- page,
475
- totalPages: tag.totalPages,
476
- noindex: tag.totalPages <= 1, // noindex thin tag pages
477
- }, siteConfig);
478
-
479
- return metadata;
480
- }
481
- ```
482
-
483
- ### Article with FAQ Section
484
-
485
- ```tsx
486
- // app/blog/[slug]/page.tsx — If the article has an FAQ section
487
- import { generateArticleMetadata, generateFAQSchema, JsonLd } from '@masters-ws/react-seo/core';
488
-
489
- export default async function ArticlePage({ params }) {
490
- const article = await fetchArticle(params.slug);
491
- const { schemas: articleSchemas } = generateArticleMetadata({ /* ... */ }, siteConfig);
492
-
493
- // Add FAQ schema if article has FAQ section
494
- const allSchemas = [...articleSchemas];
495
- if (article.faqs?.length > 0) {
496
- allSchemas.push(generateFAQSchema(
497
- article.faqs.map(f => ({ q: f.question, a: f.answer }))
498
- ));
499
- }
500
-
501
- return (
502
- <>
503
- <JsonLd schema={allSchemas} graph />
504
- <article>
505
- <h1>{article.title}</h1>
506
- <div>{article.content}</div>
507
- {article.faqs?.length > 0 && (
508
- <section>
509
- <h2>Frequently Asked Questions</h2>
510
- {article.faqs.map(faq => (
511
- <details key={faq.question}>
512
- <summary>{faq.question}</summary>
513
- <p>{faq.answer}</p>
514
- </details>
515
- ))}
516
- </section>
517
- )}
518
- </article>
519
- </>
520
- );
521
- }
522
- ```
523
-
524
- ---
525
-
526
- ## 🏢 Use Case: Showcase / Branding Website
527
-
528
- A complete guide for corporate sites, portfolios, landing pages, and service-based businesses.
529
-
530
- ### Homepage
531
-
532
- ```tsx
533
- // app/page.tsx — Server Component
534
- import { generateHomepageMetadata, JsonLd } from '@masters-ws/react-seo/core';
535
-
536
- const siteConfig = {
537
- name: "Acme Solutions",
538
- url: "https://acme.com",
539
- logo: "https://acme.com/logo.png",
540
- description: "Digital solutions for modern businesses",
541
- language: "en_US",
542
- twitterHandle: "@acme",
543
- socialLinks: [
544
- "https://twitter.com/acme",
545
- "https://linkedin.com/company/acme",
546
- "https://github.com/acme",
547
- ],
548
- };
549
-
550
- export async function generateMetadata() {
551
- const { metadata } = generateHomepageMetadata({
552
- title: "Acme Solutions — Digital Solutions for Modern Businesses",
553
- description: "We help businesses grow with cutting-edge web development, mobile apps, and cloud solutions.",
554
- ogImage: "https://acme.com/og-image.jpg",
555
-
556
- // Add LocalBusiness if you have a physical office
557
- localBusiness: {
558
- name: "Acme Solutions HQ",
559
- description: "Software development company",
560
- telephone: "+1-555-123-4567",
561
- address: {
562
- street: "123 Tech Avenue",
563
- city: "San Francisco",
564
- region: "CA",
565
- postalCode: "94105",
566
- country: "US",
567
- },
568
- geo: { lat: 37.7749, lng: -122.4194 },
569
- openingHours: ["Mo-Fr 09:00-18:00"],
570
- },
571
- }, siteConfig);
572
-
573
- return metadata;
574
- }
575
-
576
- export default function HomePage() {
577
- const { schemas } = generateHomepageMetadata({ /* same data */ }, siteConfig);
578
-
579
- return (
580
- <>
581
- <JsonLd schema={schemas} graph />
582
- <main>
583
- <section id="hero">
584
- <h1>Build. Scale. Succeed.</h1>
585
- <p>Digital solutions for modern businesses</p>
586
- </section>
587
- <section id="services">...</section>
588
- <section id="portfolio">...</section>
589
- <section id="contact">...</section>
590
- </main>
591
- </>
592
- );
593
- }
594
- ```
595
-
596
- **What Google sees for a local business:**
597
- ```json
598
- {
599
- "@context": "https://schema.org",
600
- "@graph": [
601
- { "@type": "WebPage", "name": "Acme Solutions", "url": "https://acme.com" },
602
- { "@type": "Organization", "name": "Acme Solutions", "logo": "...", "sameAs": [...] },
603
- { "@type": "WebSite", "potentialAction": { "@type": "SearchAction", ... } },
604
- {
605
- "@type": "LocalBusiness",
606
- "name": "Acme Solutions HQ",
607
- "telephone": "+1-555-123-4567",
608
- "address": { "@type": "PostalAddress", "streetAddress": "123 Tech Avenue", ... },
609
- "geo": { "@type": "GeoCoordinates", "latitude": 37.7749, "longitude": -122.4194 },
610
- "openingHours": ["Mo-Fr 09:00-18:00"]
611
- }
612
- ]
613
- }
614
- ```
615
-
616
- ### About Page
617
-
618
- ```tsx
619
- // app/about/page.tsx — Server Component
620
- import {
621
- toNextMetadata,
622
- generateWebPageSchema,
623
- generateOrganizationSchema,
624
- generateBreadcrumbSchema,
625
- JsonLd
626
- } from '@masters-ws/react-seo/core';
627
-
628
- export async function generateMetadata() {
629
- return toNextMetadata({
630
- title: "About Us — Our Story & Mission",
631
- description: "Learn about Acme Solutions, our mission, team, and the values that drive us to build amazing digital products.",
632
- image: "https://acme.com/about-og.jpg",
633
- type: 'website',
634
- canonical: "https://acme.com/about",
635
- }, siteConfig);
636
- }
637
-
638
- export default function AboutPage() {
639
- const schemas = [
640
- generateWebPageSchema({
641
- name: "About Acme Solutions",
642
- description: "Our story, mission, and team",
643
- url: "https://acme.com/about",
644
- image: "https://acme.com/team-photo.jpg",
645
- }, siteConfig),
646
- generateBreadcrumbSchema([
647
- { name: "Home", item: "https://acme.com" },
648
- { name: "About Us", item: "https://acme.com/about" },
649
- ]),
650
- generateOrganizationSchema(siteConfig),
651
- ];
652
-
653
- return (
654
- <>
655
- <JsonLd schema={schemas} graph />
656
- <main>
657
- <h1>About Us</h1>
658
- <p>Founded in 2020, Acme Solutions...</p>
659
- </main>
660
- </>
661
- );
662
- }
663
- ```
664
-
665
- ### Services Page
666
-
667
- ```tsx
668
- // app/services/page.tsx — Server Component
669
- import {
670
- toNextMetadata,
671
- generateWebPageSchema,
672
- generateBreadcrumbSchema,
673
- generateFAQSchema,
674
- JsonLd
675
- } from '@masters-ws/react-seo/core';
676
-
677
- export async function generateMetadata() {
678
- return toNextMetadata({
679
- title: "Our Services — Web Development, Mobile Apps & Cloud",
680
- description: "Explore our professional services: custom web development, mobile app design, cloud infrastructure, and digital consulting.",
681
- image: "https://acme.com/services-og.jpg",
682
- type: 'website',
683
- canonical: "https://acme.com/services",
684
- }, siteConfig);
685
- }
686
-
687
- export default function ServicesPage() {
688
- const schemas = [
689
- generateWebPageSchema({
690
- name: "Our Services",
691
- description: "Professional digital services",
692
- url: "https://acme.com/services",
693
- }, siteConfig),
694
- generateBreadcrumbSchema([
695
- { name: "Home", item: "https://acme.com" },
696
- { name: "Services", item: "https://acme.com/services" },
697
- ]),
698
- // Add FAQ for common service questions → shows in Google rich results!
699
- generateFAQSchema([
700
- { q: "What technologies do you use?", a: "We specialize in React, Next.js, Node.js, and cloud platforms like AWS and GCP." },
701
- { q: "How long does a typical project take?", a: "Most projects take 4-12 weeks depending on complexity." },
702
- { q: "Do you offer ongoing maintenance?", a: "Yes, we offer monthly maintenance plans starting at $500/month." },
703
- ]),
704
- ];
705
-
706
- return (
707
- <>
708
- <JsonLd schema={schemas} graph />
709
- <main>
710
- <h1>Our Services</h1>
711
- <ServiceCard title="Web Development" description="..." />
712
- <ServiceCard title="Mobile Apps" description="..." />
713
- <ServiceCard title="Cloud Solutions" description="..." />
714
-
715
- <section>
716
- <h2>Frequently Asked Questions</h2>
717
- {/* FAQ content */}
718
- </section>
719
- </main>
720
- </>
721
- );
722
- }
723
- ```
724
-
725
- ### Individual Service Page
726
-
727
- ```tsx
728
- // app/services/[slug]/page.tsx — Server Component
729
- import {
730
- toNextMetadata,
731
- generateWebPageSchema,
732
- generateBreadcrumbSchema,
733
- generateHowToSchema,
734
- generateFAQSchema,
735
- JsonLd
736
- } from '@masters-ws/react-seo/core';
737
-
738
- export async function generateMetadata({ params }) {
739
- const service = await fetchService(params.slug);
740
-
741
- return toNextMetadata({
742
- title: `${service.name} — Professional ${service.name} Services`,
743
- description: service.meta_description,
744
- image: service.og_image,
745
- type: 'website',
746
- canonical: `https://acme.com/services/${params.slug}`,
747
- }, siteConfig);
748
- }
749
-
750
- export default async function ServicePage({ params }) {
751
- const service = await fetchService(params.slug);
752
-
753
- const schemas = [
754
- generateWebPageSchema({
755
- name: service.name,
756
- description: service.description,
757
- url: `https://acme.com/services/${params.slug}`,
758
- }, siteConfig),
759
- generateBreadcrumbSchema([
760
- { name: "Home", item: "https://acme.com" },
761
- { name: "Services", item: "https://acme.com/services" },
762
- { name: service.name, item: `https://acme.com/services/${params.slug}` },
763
- ]),
764
- ];
765
-
766
- // Add HowTo schema if the service has a process
767
- if (service.process_steps) {
768
- schemas.push(generateHowToSchema({
769
- name: `How We Deliver ${service.name}`,
770
- description: `Our step-by-step process for ${service.name}`,
771
- steps: service.process_steps.map(s => ({
772
- name: s.title,
773
- text: s.description,
774
- })),
775
- }));
776
- }
777
-
778
- // Add FAQ if available
779
- if (service.faqs?.length > 0) {
780
- schemas.push(generateFAQSchema(
781
- service.faqs.map(f => ({ q: f.question, a: f.answer }))
782
- ));
783
- }
784
-
785
- return (
786
- <>
787
- <JsonLd schema={schemas} graph />
788
- <main>
789
- <h1>{service.name}</h1>
790
- <div>{service.content}</div>
791
- </main>
792
- </>
793
- );
794
- }
795
- ```
796
-
797
- ### Contact Page
798
-
799
- ```tsx
800
- // app/contact/page.tsx — Server Component
801
- import {
802
- toNextMetadata,
803
- generateWebPageSchema,
804
- generateBreadcrumbSchema,
805
- generateLocalBusinessSchema,
806
- JsonLd
807
- } from '@masters-ws/react-seo/core';
808
-
809
- export async function generateMetadata() {
810
- return toNextMetadata({
811
- title: "Contact Us — Get In Touch",
812
- description: "Have a project in mind? Contact Acme Solutions for a free consultation. We respond within 24 hours.",
813
- type: 'website',
814
- canonical: "https://acme.com/contact",
815
- }, siteConfig);
816
- }
817
-
818
- export default function ContactPage() {
819
- const schemas = [
820
- generateWebPageSchema({
821
- name: "Contact Us",
822
- description: "Get in touch with our team",
823
- url: "https://acme.com/contact",
824
- }, siteConfig),
825
- generateBreadcrumbSchema([
826
- { name: "Home", item: "https://acme.com" },
827
- { name: "Contact", item: "https://acme.com/contact" },
828
- ]),
829
- generateLocalBusinessSchema({
830
- name: "Acme Solutions",
831
- description: "Digital solutions company",
832
- telephone: "+1-555-123-4567",
833
- address: {
834
- street: "123 Tech Avenue",
835
- city: "San Francisco",
836
- region: "CA",
837
- postalCode: "94105",
838
- country: "US",
839
- },
840
- geo: { lat: 37.7749, lng: -122.4194 },
841
- openingHours: ["Mo-Fr 09:00-18:00"],
842
- }),
843
- ];
844
-
845
- return (
846
- <>
847
- <JsonLd schema={schemas} graph />
848
- <main>
849
- <h1>Contact Us</h1>
850
- <ContactForm />
851
- <Map />
852
- </main>
853
- </>
854
- );
855
- }
856
- ```
857
-
858
- ### Portfolio / Case Study Page
859
-
860
- ```tsx
861
- // app/portfolio/[slug]/page.tsx
862
- import {
863
- toNextMetadata,
864
- generateWebPageSchema,
865
- generateBreadcrumbSchema,
866
- JsonLd,
867
- cleanSchema,
868
- } from '@masters-ws/react-seo/core';
869
-
870
- export async function generateMetadata({ params }) {
871
- const project = await fetchProject(params.slug);
872
-
873
- return toNextMetadata({
874
- title: `${project.name} — Case Study`,
875
- description: project.summary,
876
- image: project.cover_image,
877
- type: 'website',
878
- canonical: `https://acme.com/portfolio/${params.slug}`,
879
- }, siteConfig);
880
- }
881
-
882
- export default async function CaseStudyPage({ params }) {
883
- const project = await fetchProject(params.slug);
884
-
885
- const schemas = [
886
- generateWebPageSchema({
887
- name: project.name,
888
- description: project.summary,
889
- url: `https://acme.com/portfolio/${params.slug}`,
890
- image: project.cover_image,
891
- datePublished: project.completed_at,
892
- }, siteConfig),
893
- generateBreadcrumbSchema([
894
- { name: "Home", item: "https://acme.com" },
895
- { name: "Portfolio", item: "https://acme.com/portfolio" },
896
- { name: project.name, item: `https://acme.com/portfolio/${params.slug}` },
897
- ]),
898
- // Custom CreativeWork schema for portfolio items
899
- cleanSchema({
900
- "@context": "https://schema.org",
901
- "@type": "CreativeWork",
902
- "name": project.name,
903
- "description": project.summary,
904
- "image": project.cover_image,
905
- "dateCreated": project.completed_at,
906
- "creator": {
907
- "@type": "Organization",
908
- "name": siteConfig.name,
909
- },
910
- "url": `https://acme.com/portfolio/${params.slug}`,
911
- }),
912
- ];
913
-
914
- return (
915
- <>
916
- <JsonLd schema={schemas} graph />
917
- <main>
918
- <h1>{project.name}</h1>
919
- <p>{project.summary}</p>
920
- </main>
921
- </>
922
- );
923
- }
924
- ```
925
-
926
- ### Careers / Job Listing Page
927
-
928
- ```tsx
929
- // app/careers/[slug]/page.tsx
930
- import {
931
- toNextMetadata,
932
- generateJobPostingSchema,
933
- generateBreadcrumbSchema,
934
- JsonLd
935
- } from '@masters-ws/react-seo/core';
936
-
937
- export async function generateMetadata({ params }) {
938
- const job = await fetchJob(params.slug);
939
-
940
- return toNextMetadata({
941
- title: `${job.title} — Join Our Team`,
942
- description: `We're hiring a ${job.title}. ${job.summary}`,
943
- type: 'website',
944
- canonical: `https://acme.com/careers/${params.slug}`,
945
- }, siteConfig);
946
- }
947
-
948
- export default async function JobPage({ params }) {
949
- const job = await fetchJob(params.slug);
950
-
951
- const schemas = [
952
- generateJobPostingSchema({
953
- title: job.title,
954
- description: job.full_description,
955
- datePosted: job.posted_at,
956
- validThrough: job.expires_at,
957
- employmentType: job.type, // "FULL_TIME", "CONTRACTOR", etc.
958
- remote: job.is_remote,
959
- hiringOrganization: {
960
- name: siteConfig.name,
961
- sameAs: siteConfig.url,
962
- logo: siteConfig.logo,
963
- },
964
- jobLocation: {
965
- streetAddress: "123 Tech Avenue",
966
- addressLocality: "San Francisco",
967
- addressRegion: "CA",
968
- addressCountry: "US",
969
- },
970
- baseSalary: job.salary ? {
971
- currency: "USD",
972
- value: { minValue: job.salary.min, maxValue: job.salary.max },
973
- unitText: "YEAR",
974
- } : undefined,
975
- }),
976
- generateBreadcrumbSchema([
977
- { name: "Home", item: "https://acme.com" },
978
- { name: "Careers", item: "https://acme.com/careers" },
979
- { name: job.title, item: `https://acme.com/careers/${params.slug}` },
980
- ]),
981
- ];
982
-
983
- return (
984
- <>
985
- <JsonLd schema={schemas} graph />
986
- <main>
987
- <h1>{job.title}</h1>
988
- <JobDetails job={job} />
989
- <ApplyButton />
990
- </main>
991
- </>
992
- );
993
- }
994
- ```
995
-
996
- ---
997
-
998
- ## 🛒 Use Case: E-Commerce Store
999
-
1000
- See the [Quick Start](#-quick-start-nextjs-app-router-recommended) section above for complete examples:
1001
- - **[Product Page](#product-page-one-call-setup)** — with reviews, return policy, shipping, variants
1002
- - **[Category Page](#category-page-with-pagination)** — with pagination and ItemList schema
1003
-
1004
- ---
1005
-
1006
- ## Manual Approach (More Control)
1007
-
1008
- Use individual functions for granular control:
1009
-
1010
- ```tsx
1011
- import {
1012
- toNextMetadata,
1013
- generateProductSchema,
1014
- generateBreadcrumbSchema,
1015
- generateOrganizationSchema,
1016
- generateWebPageSchema,
1017
- JsonLd
1018
- } from '@masters-ws/react-seo/core';
1019
-
1020
- export async function generateMetadata({ params }) {
1021
- const product = await fetchProduct(params.slug);
1022
-
1023
- return toNextMetadata({
1024
- title: product.meta_title || product.name,
1025
- description: product.meta_description,
1026
- image: product.main_image?.url,
1027
- type: 'product',
1028
- canonical: `https://store.com/products/${params.slug}`,
1029
- product: {
1030
- sku: product.sku,
1031
- brand: product.brand?.name,
1032
- price: product.price,
1033
- currency: "USD",
1034
- availability: "https://schema.org/InStock",
1035
- }
1036
- }, siteConfig);
1037
- }
1038
-
1039
- export default async function ProductPage({ params }) {
1040
- const product = await fetchProduct(params.slug);
1041
-
1042
- const schemas = [
1043
- generateProductSchema({
1044
- name: product.name,
1045
- description: product.description,
1046
- image: [product.main_image, ...product.gallery],
1047
- price: product.price,
1048
- currency: "USD",
1049
- url: `https://store.com/products/${params.slug}`,
1050
- reviews: [
1051
- { author: "John", ratingValue: 5, reviewBody: "Excellent product!" },
1052
- ],
1053
- returnPolicy: {
1054
- returnPolicyCategory: 'MerchantReturnFiniteReturnWindow',
1055
- returnWithin: 14,
1056
- },
1057
- shipping: {
1058
- shippingRate: { value: 0, currency: "USD" },
1059
- deliveryTime: { minDays: 2, maxDays: 5 },
1060
- },
1061
- }),
1062
- generateBreadcrumbSchema([
1063
- { name: "Home", item: "https://store.com" },
1064
- { name: product.name, item: `https://store.com/products/${params.slug}` },
1065
- ]),
1066
- ];
1067
-
1068
- return (
1069
- <>
1070
- <JsonLd schema={schemas} graph />
1071
- <ProductDetailClient product={product} />
1072
- </>
1073
- );
1074
- }
1075
- ```
1076
-
1077
- ---
1078
-
1079
- ## `<JsonLd>` Component
1080
-
1081
- Server-safe JSON-LD renderer. Works in Server Components (no `'use client'` needed).
1082
-
1083
- ### Props
1084
-
1085
- | Prop | Type | Default | Description |
1086
- | :--- | :--- | :--- | :--- |
1087
- | `schema` | `object \| object[]` | — | Schema object(s) to render |
1088
- | `graph` | `boolean` | `false` | Combine schemas into a single `@graph` block |
1089
-
1090
- ### Separate Scripts (default)
1091
- ```tsx
1092
- <JsonLd schema={[productSchema, breadcrumbSchema]} />
1093
- // Renders 2 separate <script type="application/ld+json"> tags
1094
- ```
1095
-
1096
- ### @graph Pattern (recommended for multiple schemas)
1097
- ```tsx
1098
- <JsonLd schema={[productSchema, breadcrumbSchema, orgSchema]} graph />
1099
- // Renders 1 <script> with @context + @graph containing all schemas
1100
- ```
1101
-
1102
- ---
1103
-
1104
- ## Usage: React / Pages Router (With Components)
1105
-
1106
- ```tsx
1107
- // _app.tsx
1108
- import { SEOProvider } from '@masters-ws/react-seo';
1109
-
1110
- const config = { name: "My Site", url: "https://mysite.com", description: "Awesome site" };
1111
-
1112
- export default function App({ Component, pageProps }) {
1113
- return (
1114
- <SEOProvider config={config}>
1115
- <Component {...pageProps} />
1116
- </SEOProvider>
1117
- );
1118
- }
1119
- ```
1120
-
1121
- ```tsx
1122
- // pages/products/[id].tsx
1123
- import { SeoProduct } from '@masters-ws/react-seo';
1124
-
1125
- export default function ProductPage({ product }) {
1126
- return (
1127
- <>
1128
- <SeoProduct item={{
1129
- name: product.name,
1130
- description: product.description,
1131
- image: product.image,
1132
- price: product.price,
1133
- currency: "USD",
1134
- sku: product.sku,
1135
- brand: product.brand,
1136
- }} />
1137
- <main>...</main>
1138
- </>
1139
- );
1140
- }
1141
- ```
1142
-
1143
- ---
1144
-
1145
- ## Core Functions Reference
1146
-
1147
- ### Convenience Helpers (Recommended — One-Call Setup)
1148
-
1149
- | Function | Description |
1150
- | :--- | :--- |
1151
- | `generateProductMetadata(product, config)` | Generates Metadata + Product, Breadcrumb, Organization, WebSite schemas |
1152
- | `generateArticleMetadata(article, config)` | Generates Metadata + Article, Breadcrumb, Organization, WebSite schemas |
1153
- | `generateCategoryMetadata(category, config)` | Generates Metadata + CollectionPage, Breadcrumb, Organization, ItemList schemas |
1154
- | `generateHomepageMetadata(input, config)` | Generates Metadata + WebPage, Organization, WebSite, optional LocalBusiness schemas |
1155
-
1156
- ### Metadata Functions
1157
-
1158
- | Function | Description |
1159
- | :--- | :--- |
1160
- | `toNextMetadata(data, config)` | Converts SEO data to Next.js Metadata object (OG, Twitter, Canonical, etc.) |
1161
- | `generatePaginationLinks(url, page, total)` | Returns `prev` / `next` / `canonical` URLs |
1162
- | `generatePaginatedTitle(title, page, suffix)` | Appends page number to title |
1163
-
1164
- ### Schema Generator Functions
1165
-
1166
- | Function | Description |
1167
- | :--- | :--- |
1168
- | `generateProductSchema(data)` | `Product` with multi-image, reviews, return policy, shipping, variants |
1169
- | `generateArticleSchema(data, config)` | `NewsArticle` with author and publisher |
1170
- | `generateFAQSchema(questions)` | `FAQPage` |
1171
- | `generateBreadcrumbSchema(items)` | `BreadcrumbList` |
1172
- | `generateVideoSchema(data)` | `VideoObject` |
1173
- | `generateEventSchema(data)` | `Event` (online & offline) |
1174
- | `generateLocalBusinessSchema(data)` | `LocalBusiness` with geo and opening hours |
1175
- | `generateOrganizationSchema(config)` | `Organization` |
1176
- | `generateWebSiteSchema(config)` | `WebSite` with `SearchAction` |
1177
- | `generateWebPageSchema(data, config)` | `WebPage` |
1178
- | `generateCollectionPageSchema(data, config)` | `CollectionPage` (for categories/archives) |
1179
- | `generateItemListSchema(data)` | `ItemList` (for product listings) |
1180
- | `generateHowToSchema(data)` | `HowTo` with steps, tools, supplies |
1181
- | `generateRecipeSchema(data)` | `Recipe` with ingredients and instructions |
1182
- | `generateJobPostingSchema(data)` | `JobPosting` with salary and remote support |
1183
- | `generateSoftwareSchema(data)` | `SoftwareApplication` |
1184
- | `generateBookSchema(data)` | `Book` |
1185
- | `generateMovieSchema(data)` | `Movie` |
1186
- | `generatePodcastSchema(data)` | `PodcastSeries` |
1187
- | `generatePodcastEpisodeSchema(data)` | `PodcastEpisode` |
1188
-
1189
- ### Utility Functions
1190
-
1191
- | Function | Description |
1192
- | :--- | :--- |
1193
- | `cleanSchema(obj)` | Deep-removes `undefined`/`null` values from schema objects |
1194
- | `validateSEO(type, data, fields)` | Logs dev warnings for missing required fields |
1195
-
1196
- ### Components
1197
-
1198
- | Component | Description |
1199
- | :--- | :--- |
1200
- | `<JsonLd schema={..} graph? />` | Server-safe JSON-LD renderer with optional `@graph` support |
1201
-
1202
- ---
1203
-
1204
- ## Product Schema Features
1205
-
1206
- ### Multi-Image Support
1207
- ```tsx
1208
- generateProductSchema({
1209
- name: "T-Shirt",
1210
- image: [ // Array of images
1211
- "https://store.com/front.jpg",
1212
- "https://store.com/back.jpg",
1213
- "https://store.com/detail.jpg",
1214
- ],
1215
- // ...
1216
- });
1217
- ```
1218
-
1219
- ### Individual Reviews
1220
- ```tsx
1221
- generateProductSchema({
1222
- // ...
1223
- rating: 4.5,
1224
- reviewCount: 128,
1225
- reviews: [
1226
- { author: "Alice", ratingValue: 5, reviewBody: "Amazing quality!", datePublished: "2024-01-15" },
1227
- { author: "Bob", ratingValue: 4, reviewBody: "Good value", datePublished: "2024-02-01" },
1228
- ],
1229
- });
1230
- ```
1231
-
1232
- ### Return Policy
1233
- ```tsx
1234
- generateProductSchema({
1235
- // ...
1236
- returnPolicy: {
1237
- returnPolicyCategory: 'MerchantReturnFiniteReturnWindow',
1238
- returnWithin: 30, // 30 days
1239
- returnMethod: 'ReturnByMail',
1240
- returnFees: 'FreeReturn',
1241
- },
1242
- });
1243
- ```
1244
-
1245
- ### Shipping Details
1246
- ```tsx
1247
- generateProductSchema({
1248
- // ...
1249
- shipping: {
1250
- shippingRate: { value: 5.99, currency: "USD" },
1251
- shippingDestination: "US",
1252
- deliveryTime: { minDays: 3, maxDays: 7 },
1253
- freeShippingThreshold: 50, // Free shipping over $50
1254
- },
1255
- });
1256
- ```
1257
-
1258
- ### Product Variants (AggregateOffer)
1259
- ```tsx
1260
- generateProductSchema({
1261
- name: "T-Shirt",
1262
- description: "...",
1263
- // When variants are provided, generates AggregateOffer with lowPrice/highPrice
1264
- variants: [
1265
- { name: "Small", sku: "TS-S", price: 19.99 },
1266
- { name: "Medium", sku: "TS-M", price: 22.99 },
1267
- { name: "Large", sku: "TS-L", price: 24.99 },
1268
- ],
1269
- });
1270
- // Result: AggregateOffer { lowPrice: 19.99, highPrice: 24.99, offerCount: 3 }
1271
- ```
1272
-
1273
- ---
1274
-
1275
- ## Development Warnings
1276
-
1277
- In development mode (`NODE_ENV !== 'production'`), the library logs warnings for missing required fields:
1278
-
1279
- ```
1280
- [react-seo] Warning: "image" is missing in Product schema. Google may not show rich results.
1281
- [react-seo] Warning: "price" is missing in Product schema. Google may not show rich results.
1282
- ```
1283
-
1284
- ---
1285
-
1286
- ## ⚠️ Important: SSR vs CSR
1287
-
1288
- ### ✅ Correct — Server-Side (Google sees everything on first crawl)
1289
- ```tsx
1290
- // page.tsx — NO 'use client'!
1291
- import { generateProductMetadata, JsonLd } from '@masters-ws/react-seo/core';
1292
-
1293
- export async function generateMetadata() { /* ... */ }
1294
-
1295
- export default function Page() {
1296
- return <JsonLd schema={schemas} graph />;
1297
- }
1298
- ```
1299
-
1300
- ### ❌ Incorrect — Client-Side (Google may not see metadata)
1301
- ```tsx
1302
- // page.tsx
1303
- 'use client' // ⛔ Metadata is only injected after JavaScript loads!
1304
-
1305
- export default function Page() {
1306
- return <SEO title="..." />; // Uses react-helmet → client-side only
1307
- }
1308
- ```
1309
-
1310
- > **Rule of thumb:** For Next.js App Router, always use `@masters-ws/react-seo/core` functions
1311
- > in Server Components. Use `'use client'` only for interactive UI components,
1312
- > never for SEO logic.
1313
-
1314
- ---
1315
-
1316
- ## Helmet Components Reference (Requires `react-helmet-async`)
1317
-
1318
- These components are designed for React SPA or Next.js Pages Router:
1319
-
1320
- | Component | Description |
1321
- | :--- | :--- |
1322
- | `<SEOProvider>` | Context provider — wraps your app with site config |
1323
- | `<SEO />` | Main component for meta tags and inline schemas |
1324
- | `<SeoArticle />` | Article/blog post SEO |
1325
- | `<SeoProduct />` | E-commerce product SEO |
1326
- | `<SeoFAQ />` | FAQ pages |
1327
- | `<SeoVideo />` | Video content |
1328
- | `<SeoEvent />` | Events and conferences |
1329
- | `<SeoLocalBusiness />` | Physical business locations |
1330
- | `<SeoCategory />` | Category pages with pagination |
1331
- | `<SeoTag />` | Tag pages with pagination |
1332
- | `<SeoAuthor />` | Author profile pages |
1333
- | `<SeoHowTo />` | How-to guides |
1334
- | `<SeoReview />` | Review pages |
1335
- | `<SeoCourse />` | Online courses |
1336
- | `<SeoRecipe />` | Recipes |
1337
- | `<SeoJobPosting />` | Job listings |
1338
- | `<Breadcrumb />` | Breadcrumb navigation with schema |
1339
-
1340
- > **Note:** `<SeoTag>`, `<SeoAuthor>`, and `<SeoCategory>` accept `pageSuffix` and `titlePrefix` props
1341
- > for localization (defaults to English). Pass `pageSuffix="صفحة"` for Arabic, etc.
1342
-
1343
- ---
1344
-
1345
- ## Configuration
1346
-
1347
- ### SiteConfig
1348
-
1349
- ```typescript
1350
- interface SiteConfig {
1351
- name: string; // Site name (used in title suffix)
1352
- url: string; // Base URL (no trailing slash)
1353
- description: string; // Default meta description
1354
- logo?: string; // Logo URL (used in Organization schema)
1355
- language?: string; // Locale, e.g. 'en_US' (default: 'ar_SA')
1356
- twitterHandle?: string; // Twitter @username
1357
- facebookAppId?: string; // Facebook App ID
1358
- themeColor?: string; // Mobile browser theme color
1359
- manifest?: string; // Web app manifest URL
1360
- socialLinks?: string[]; // Social media profile URLs
1361
- publisher?: string; // Publisher name
1362
- }
1363
- ```
1364
-
1365
- ### SEOData
1366
-
1367
- ```typescript
1368
- interface SEOData {
1369
- title?: string;
1370
- description?: string;
1371
- image?: string;
1372
- canonical?: string;
1373
- type?: 'website' | 'article' | 'product' | 'profile' | 'video' | 'faq';
1374
- robots?: string;
1375
- noindex?: boolean;
1376
- keywords?: string[];
1377
- prev?: string;
1378
- next?: string;
1379
-
1380
- // Open Graph
1381
- ogTitle?: string;
1382
- ogDescription?: string;
1383
- ogImage?: string;
1384
- ogImageWidth?: number; // Default: 1200
1385
- ogImageHeight?: number; // Default: 630
1386
- ogImageAlt?: string;
1387
- ogType?: string;
1388
- ogLocale?: string;
1389
-
1390
- // Twitter Cards
1391
- twitterCard?: 'summary' | 'summary_large_image' | 'app' | 'player';
1392
- twitterTitle?: string;
1393
- twitterDescription?: string;
1394
- twitterImage?: string;
1395
-
1396
- // Article-specific
1397
- publishedTime?: string;
1398
- modifiedTime?: string;
1399
- author?: { name: string; url?: string; image?: string };
1400
- tags?: string[];
1401
- section?: string;
1402
- readingTime?: number;
1403
-
1404
- // Product-specific
1405
- product?: {
1406
- sku?: string;
1407
- brand?: string;
1408
- price?: number;
1409
- currency?: string;
1410
- availability?: string;
1411
- rating?: number;
1412
- reviewCount?: number;
1413
- };
1414
-
1415
- // Multilingual
1416
- alternates?: Array<{ hreflang: string; href: string }>;
1417
-
1418
- // Performance
1419
- dnsPrefetch?: string[];
1420
- preconnect?: string[];
1421
- prefetch?: string[];
1422
- preload?: Array<{ href: string; as: string; type?: string }>;
1423
-
1424
- // Extras
1425
- whatsappImage?: string;
1426
- schema?: any;
1427
- }
1428
- ```
1429
-
1430
- ---
1431
-
1432
- ## Changelog
1433
-
1434
- ### v1.4.0
1435
- - ✨ **generateHomepageMetadata()** — One-call helper for homepage / landing pages
1436
- - ✨ **cleanSchema()** — Automatically strips `undefined`/`null` from all JSON-LD output
1437
- - ✨ **@graph pattern** — `<JsonLd graph />` combines schemas into single `@context`/`@graph` block
1438
- - ✨ **Multi-image** — Product and Article schemas accept `string | string[]` for images
1439
- - ✨ **Individual reviews** — Product schema supports `reviews[]` array with author, rating, body
1440
- - ✨ **MerchantReturnPolicy** — Product schema supports return policy (category, days, method, fees)
1441
- - ✨ **ShippingDetails** — Product schema supports shipping rate, destination, delivery time, free threshold
1442
- - ✨ **Product variants** — Automatically generates `AggregateOffer` with `lowPrice`/`highPrice`
1443
- - ✨ **generateCategoryMetadata()** — One-call helper for category pages with pagination + ItemList
1444
- - ✨ **WebPage schema** — `generateWebPageSchema()` for general pages
1445
- - ✨ **CollectionPage schema** — `generateCollectionPageSchema()` for category/archive pages
1446
- - ✨ **ItemList schema** — `generateItemListSchema()` for product listing pages
1447
- - ✨ **HowTo schema** — `generateHowToSchema()` with steps, tools, supplies (moved to core)
1448
- - ✨ **Recipe schema** — `generateRecipeSchema()` (moved to core)
1449
- - ✨ **JobPosting schema** — `generateJobPostingSchema()` with remote support (moved to core)
1450
- - ✨ **validateSEO()** — Dev-only console warnings for missing required fields
1451
- - 🔧 **i18n** — Removed all hardcoded Arabic strings, all labels now configurable via props
1452
- - 🔧 **Product schema** — Added `gtin`, `mpn`, `condition`, `seller` fields
1453
-
1454
- ### v1.3.0
1455
- - ✨ Added `<JsonLd>` server-safe component for Next.js App Router
1456
- - ✨ Added `generateProductMetadata()` — one-call helper for product pages
1457
- - ✨ Added `generateArticleMetadata()` — one-call helper for article pages
1458
- - 🔧 Enhanced `toNextMetadata()` with article OG tags, product OG tags, pagination links
1459
- - 📝 Updated README with SSR best practices
1460
-
1461
- ### v1.2.1
1462
- - Initial stable release with core functions and Helmet components
1463
-
1464
- ---
1465
-
1466
- ## 💙 Recommended: Outrank — AI-Powered SEO Platform
1467
-
1468
- Take your SEO to the next level with [**Outrank**](https://outrank.so/?via=shadi) — the AI-powered platform for creating SEO-optimized content at scale.
1469
-
1470
- - 🚀 **AI Content Generation** — Blog posts, landing pages & more
1471
- - 📊 **SEO Analysis** — Real-time optimization suggestions
1472
- - ⚡ **Works great with this package** — Generate content, we handle the technical SEO
1473
-
1474
- 🎁 **Exclusive:** Use code **`SHADI`** for **10% off** your first month.
1475
-
1476
- > Want to earn too? [Join the affiliate program](https://outrank.so/?via=shadi) — **30% lifetime recurring commission**.
1477
-
1478
- ---
1479
-
1480
- ## License
1481
-
1482
- MIT
1
+ [![npm version](https://img.shields.io/npm/v/@masters-ws/react-seo.svg?style=flat-square)](https://www.npmjs.com/package/@masters-ws/react-seo)
2
+ [![Total Downloads](https://img.shields.io/npm/dt/@masters-ws/react-seo.svg?style=flat-square)](https://www.npmjs.com/package/@masters-ws/react-seo)
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
4
+
5
+ # @masters-ws/react-seo
6
+
7
+ > Professional SEO toolkit for React & Next.js — SSR-first, zero-dependency core, 30+ schema types, and built-in development auditor.
8
+
9
+ ---
10
+
11
+ ## Why @masters-ws/react-seo?
12
+
13
+ Most SEO packages inject metadata client-side after JavaScript loads. Google crawls your page before that. This package fixes that.
14
+
15
+ | Feature | @masters-ws/react-seo | next-seo | react-helmet |
16
+ |---|---|---|---|
17
+ | Zero-dependency core | | | |
18
+ | SSR-first (App Router) | | | |
19
+ | `@graph` JSON-LD | | | |
20
+ | One-call page setup | | | |
21
+ | **Integrated SEO Auditor** | ✅ | ❌ | ❌ |
22
+ | Built-in pagination SEO | ✅ | partial | ❌ |
23
+
24
+ ---
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ # Next.js App Router (zero dependencies)
30
+ npm install @masters-ws/react-seo
31
+
32
+ # React / Next.js Pages Router
33
+ npm install @masters-ws/react-seo react-helmet-async
34
+ ```
35
+
36
+ ---
37
+
38
+ ## 🔍 SEO Auditor (Development Only)
39
+
40
+ This library includes a built-in **SEO Auditor** component designed to speed up your development workflow by providing a real-time analysis of your page's SEO status directly in your browser.
41
+
42
+ - **Real-Time Analysis:** Scans for title/description lengths, H1 counts, missing alt tags, and more.
43
+ - **Visual Previews:** Live mockups for Google Search results and Social Media cards.
44
+ - **Production Safe:** Only renders when `process.env.NODE_ENV === 'development'`.
45
+
46
+ ### Usage:
47
+
48
+ ```tsx
49
+ // app/layout.tsx (Next.js App Router)
50
+ import { SEOAuditor } from '@masters-ws/react-seo';
51
+
52
+ export default function RootLayout({ children }) {
53
+ return (
54
+ <html>
55
+ <body>
56
+ {children}
57
+ <SEOAuditor />
58
+ </body>
59
+ </html>
60
+ );
61
+ }
62
+ ```
63
+
64
+ ---
65
+
66
+ ## 🚀 Quick Start: Next.js App Router (Recommended)
67
+
68
+ ### Product Page — One-Call Setup
69
+
70
+ ```tsx
71
+ // app/products/[slug]/page.tsx
72
+ import { generateProductMetadata, JsonLd } from '@masters-ws/react-seo/core';
73
+
74
+ const siteConfig = {
75
+ name: "My Store",
76
+ url: "https://store.com",
77
+ logo: "https://store.com/logo.png",
78
+ description: "Best online store",
79
+ language: "en_US",
80
+ };
81
+
82
+ export async function generateMetadata({ params }) {
83
+ const product = await fetchProduct(params.slug);
84
+ const { metadata } = generateProductMetadata({
85
+ name: product.name,
86
+ description: product.short_description,
87
+ image: [product.main_image, ...product.gallery],
88
+ price: product.price,
89
+ currency: "USD",
90
+ sku: product.sku,
91
+ brand: product.brand?.name,
92
+ availability: product.in_stock
93
+ ? "https://schema.org/InStock"
94
+ : "https://schema.org/OutOfStock",
95
+ url: `https://store.com/products/${params.slug}`,
96
+ breadcrumbs: [
97
+ { name: "Home", item: "https://store.com" },
98
+ { name: product.category?.name, item: `https://store.com/categories/${product.category?.slug}` },
99
+ { name: product.name, item: `https://store.com/products/${params.slug}` },
100
+ ],
101
+ }, siteConfig);
102
+
103
+ return metadata;
104
+ }
105
+
106
+ export default async function ProductPage({ params }) {
107
+ const product = await fetchProduct(params.slug);
108
+ const { schemas } = generateProductMetadata({ /* same options */ }, siteConfig);
109
+
110
+ return (
111
+ <>
112
+ <JsonLd schema={schemas} graph />
113
+ <ProductDetailClient product={product} />
114
+ </>
115
+ );
116
+ }
117
+ ```
118
+
119
+ **What Google sees in the HTML source:**
120
+ ```html
121
+ <title>Product Name | My Store</title>
122
+ <meta name="description" content="..." />
123
+ <meta property="og:title" content="Product Name" />
124
+ <meta property="og:image" content="https://store.com/product.jpg" />
125
+ <link rel="canonical" href="https://store.com/products/my-product" />
126
+ <script type="application/ld+json">
127
+ {
128
+ "@context": "https://schema.org",
129
+ "@graph": [
130
+ { "@type": "Product", "name": "...", "offers": {...} },
131
+ { "@type": "BreadcrumbList", "itemListElement": [...] },
132
+ { "@type": "Organization", "name": "..." },
133
+ { "@type": "WebSite", "potentialAction": {...} }
134
+ ]
135
+ }
136
+ </script>
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Core Functions
142
+
143
+ ### One-Call Helpers (Recommended)
144
+
145
+ | Function | Generates |
146
+ |---|---|
147
+ | `generateProductMetadata(product, config)` | Metadata + Product, Breadcrumb, Organization, WebSite schemas |
148
+ | `generateArticleMetadata(article, config)` | Metadata + NewsArticle, Breadcrumb, Organization, WebSite schemas |
149
+ | `generateCategoryMetadata(category, config)` | Metadata + CollectionPage, Breadcrumb, ItemList schemas |
150
+ | `generateHomepageMetadata(input, config)` | Metadata + WebPage, Organization, WebSite, optional LocalBusiness schemas |
151
+
152
+ ### Schema Generators
153
+
154
+ | Function | Schema Type |
155
+ |---|---|
156
+ | `generateProductSchema(data)` | `Product` multi-image, reviews, return policy, shipping, variants |
157
+ | `generateArticleSchema(data, config)` | `NewsArticle` |
158
+ | `generateFAQSchema(questions)` | `FAQPage` |
159
+ | `generateBreadcrumbSchema(items)` | `BreadcrumbList` |
160
+ | `generateLocalBusinessSchema(data)` | `LocalBusiness` |
161
+ | `generateOrganizationSchema(config)` | `Organization` |
162
+ | `generateWebSiteSchema(config)` | `WebSite` with `SearchAction` |
163
+ | `generateWebPageSchema(data, config)` | `WebPage` |
164
+ | `generateHowToSchema(data)` | `HowTo` |
165
+ | `generateRecipeSchema(data)` | `Recipe` |
166
+ | `generateJobPostingSchema(data)` | `JobPosting` |
167
+ | `generateEventSchema(data)` | `Event` |
168
+ | `generateVideoSchema(data)` | `VideoObject` |
169
+
170
+ ### Utilities
171
+
172
+ | Function | Description |
173
+ |---|---|
174
+ | `toNextMetadata(data, config)` | Converts SEO data to Next.js Metadata object |
175
+ | `cleanSchema(obj)` | Removes `undefined`/`null` from schema objects |
176
+ | `validateSEO(type, data, fields)` | Dev-only warnings for missing required fields |
177
+
178
+ ### `<JsonLd>` Component
179
+
180
+ ```tsx
181
+ // Multiple separate <script> tags
182
+ <JsonLd schema={[productSchema, breadcrumbSchema]} />
183
+
184
+ // Single @graph block (Google recommended)
185
+ <JsonLd schema={[productSchema, breadcrumbSchema, orgSchema]} graph />
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Product Schema — Advanced Features
191
+
192
+ ### Reviews + Ratings
193
+ ```tsx
194
+ generateProductSchema({
195
+ name: "T-Shirt",
196
+ rating: 4.5,
197
+ reviewCount: 128,
198
+ reviews: [
199
+ { author: "Alice", ratingValue: 5, reviewBody: "Amazing quality!", datePublished: "2024-01-15" },
200
+ { author: "Bob", ratingValue: 4, reviewBody: "Good value", datePublished: "2024-02-01" },
201
+ ],
202
+ });
203
+ ```
204
+
205
+ ### Return Policy
206
+ ```tsx
207
+ generateProductSchema({
208
+ returnPolicy: {
209
+ returnPolicyCategory: "MerchantReturnFiniteReturnWindow",
210
+ returnWithin: 30,
211
+ returnMethod: "ReturnByMail",
212
+ returnFees: "FreeReturn",
213
+ },
214
+ });
215
+ ```
216
+
217
+ ### Shipping
218
+ ```tsx
219
+ generateProductSchema({
220
+ shipping: {
221
+ shippingRate: { value: 5.99, currency: "USD" },
222
+ shippingDestination: "US",
223
+ deliveryTime: { minDays: 3, maxDays: 7 },
224
+ freeShippingThreshold: 50,
225
+ },
226
+ });
227
+ ```
228
+
229
+ ### Product Variants → AggregateOffer
230
+ ```tsx
231
+ generateProductSchema({
232
+ variants: [
233
+ { name: "Small", sku: "TS-S", price: 19.99 },
234
+ { name: "Medium", sku: "TS-M", price: 22.99 },
235
+ { name: "Large", sku: "TS-L", price: 24.99 },
236
+ ],
237
+ });
238
+ // → AggregateOffer { lowPrice: 19.99, highPrice: 24.99, offerCount: 3 }
239
+ ```
240
+
241
+ ---
242
+
243
+ ## React / Pages Router
244
+
245
+ ```tsx
246
+ // _app.tsx
247
+ import { SEOProvider } from '@masters-ws/react-seo';
248
+
249
+ export default function App({ Component, pageProps }) {
250
+ return (
251
+ <SEOProvider config={{ name: "My Site", url: "https://mysite.com", description: "..." }}>
252
+ <Component {...pageProps} />
253
+ </SEOProvider>
254
+ );
255
+ }
256
+
257
+ // pages/products/[id].tsx
258
+ import { SeoProduct } from '@masters-ws/react-seo';
259
+
260
+ export default function ProductPage({ product }) {
261
+ return (
262
+ <>
263
+ <SeoProduct item={{
264
+ name: product.name,
265
+ description: product.description,
266
+ image: product.image,
267
+ price: product.price,
268
+ currency: "USD",
269
+ sku: product.sku,
270
+ }} />
271
+ <main>...</main>
272
+ </>
273
+ );
274
+ }
275
+ ```
276
+
277
+ ---
278
+
279
+ ## ⚠️ SSR vs CSR
280
+
281
+ ```tsx
282
+ // ✅ Correct — Server Component, Google sees everything
283
+ import { generateProductMetadata, JsonLd } from '@masters-ws/react-seo/core';
284
+ export async function generateMetadata() { /* ... */ }
285
+ export default function Page() {
286
+ return <JsonLd schema={schemas} graph />;
287
+ }
288
+
289
+ // ❌ Wrong — 'use client' means metadata loads after JS
290
+ 'use client';
291
+ export default function Page() {
292
+ return <SEO title="..." />; // Google may miss this
293
+ }
294
+ ```
295
+
296
+ ---
297
+
298
+ ## Generating SEO-Optimized Content
299
+
300
+ This package handles technical SEO perfectly. For generating the **actual content** that ranks — blog posts, product descriptions, landing pages — pair it with [**Outrank**](https://outrank.so/?via=shadi), an AI platform built specifically for SEO content at scale.
301
+
302
+ Use code **`SHADI`** for 10% off your first month.
303
+
304
+ ---
305
+
306
+ ## License
307
+
308
+ MIT