@masters-ws/react-seo 1.5.0 → 1.5.2

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