@masters-ws/react-seo 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,138 +1,189 @@
1
1
  # @masters-ws/react-seo
2
2
 
3
- Professional high-performance SEO package for React and Next.js. **1:1 Feature Parity with the powerful Laravel SEO package.** Designed for high-traffic news sites, e-marketplaces, and professional blog platforms where speed and indexing are critical.
3
+ 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.
4
4
 
5
5
  ## Key Features
6
6
 
7
- - ✅ **Hybrid Implementation** - Works with `react-helmet-async` for Client/Pages Router and native `Metadata API` for Next.js App Router.
8
- - ✅ **Full Metadata Support** - Title, Description, Keywords, Robots, Canonical.
9
- - ✅ **Intelligent Pagination** - Automatic `rel="prev"` and `rel="next"` handling with clean URL logic.
10
- - ✅ **Advanced Social Meta** - OpenGraph (Facebook/LinkedIn), Twitter Cards, and WhatsApp/Telegram optimization.
11
- - ✅ **JSON-LD Schema.org (25+ Types)** - Dedicated components for `NewsArticle`, `Product`, `FAQPage`, `BreadcrumbList`, `Organization`, `WebSite`, `Event`, `HowTo`, `Recipe`, etc.
12
- - ✅ **Performance & Security** - Support for DNS Prefetch, Preconnect, Preload, and CSP.
13
- - ✅ **Multilingual Support** - Easy `hreflang` management.
14
- - ✅ **Mobile Optimized** - Theme color, Apple mobile web app capability, and manifest support.
15
- - ✅ **Analytics Integration** - Built-in hooks for GA4, GTM, and Facebook Pixel.
7
+ - ✅ **Zero-Dependency Core** - Pure functions for Next.js (no react-helmet-async needed!)
8
+ - ✅ **Hybrid Architecture** - Use functions OR components based on your needs
9
+ - ✅ **Full Metadata Support** - Title, Description, Keywords, Robots, Canonical
10
+ - ✅ **Intelligent Pagination** - Automatic `rel="prev"` and `rel="next"` handling
11
+ - ✅ **25+ Schema Types** - Article, Product, FAQ, Event, LocalBusiness, Video, Recipe, etc.
12
+ - ✅ **Multilingual Support** - Easy `hreflang` management
13
+ - ✅ **Performance Optimized** - DNS Prefetch, Preconnect, Preload support
16
14
 
17
15
  ## Installation
18
16
 
17
+ ### For Next.js App Router (Zero Dependencies):
18
+ ```bash
19
+ npm install @masters-ws/react-seo
20
+ ```
21
+
22
+ ### For React / Next.js Pages Router (With Components):
19
23
  ```bash
20
24
  npm install @masters-ws/react-seo react-helmet-async
21
25
  ```
22
26
 
23
- ## Next.js App Router (SSR - Recommended for News/Malls)
27
+ ---
28
+
29
+ ## Usage: Next.js App Router (Recommended)
24
30
 
25
- For the best performance and 100% server-side rendering, use the `toNextMetadata` adapter in your `layout.tsx` or `page.tsx`.
31
+ Use the **core functions** directly in your Server Components. No Helmet needed!
26
32
 
27
33
  ```tsx
28
- // page.tsx (Server Component)
29
- import { toNextMetadata } from '@shammaa/react-seo';
34
+ // app/news/[slug]/page.tsx
35
+ import { toNextMetadata, generateArticleSchema } from '@masters-ws/react-seo';
30
36
 
31
- const config = { name: "My News", url: "https://site.com" };
37
+ const siteConfig = {
38
+ name: "My News Site",
39
+ url: "https://mysite.com"
40
+ };
32
41
 
42
+ // This runs on the server - instant SEO!
33
43
  export async function generateMetadata({ params }) {
34
- const post = await getPost(params.id);
44
+ const post = await getPost(params.slug);
35
45
 
36
46
  return toNextMetadata({
37
47
  title: post.title,
38
48
  description: post.excerpt,
49
+ image: post.cover,
39
50
  type: 'article',
40
- canonical: `https://site.com/news/${post.slug}`,
41
- ogImage: post.image,
42
- publishedTime: post.date,
43
- readingTime: 5 // Optional: shows in Twitter preview
44
- }, config);
51
+ publishedTime: post.date
52
+ }, siteConfig);
45
53
  }
46
54
 
47
- export default function Page() {
48
- return <article>...</article>;
55
+ export default function ArticlePage({ params }) {
56
+ const post = await getPost(params.slug);
57
+
58
+ // Add JSON-LD Schema using core function
59
+ const articleSchema = generateArticleSchema({
60
+ title: post.title,
61
+ description: post.excerpt,
62
+ publishedTime: post.date,
63
+ author: { name: post.author }
64
+ }, siteConfig);
65
+
66
+ return (
67
+ <>
68
+ <script
69
+ type="application/ld+json"
70
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(articleSchema) }}
71
+ />
72
+ <article>...</article>
73
+ </>
74
+ );
49
75
  }
50
76
  ```
51
77
 
52
- ## React / Next.js Pages Router (Client/SSR)
78
+ ---
53
79
 
54
- ### 1. Provider Setup
55
- Wrap your application in `App.tsx` or `_app.tsx`:
80
+ ## Usage: React / Pages Router (With Components)
81
+
82
+ If you prefer the component approach or are using React without Next.js:
56
83
 
57
84
  ```tsx
58
- import { SEOProvider } from '@shammaa/react-seo';
85
+ // _app.tsx
86
+ import { SEOProvider } from '@masters-ws/react-seo';
59
87
 
60
- const siteConfig = {
61
- name: "My Site",
62
- url: "https://mysite.com",
63
- googleAnalyticsId: "G-XXXXXXXXXX",
64
- themeColor: "#007bff"
65
- };
88
+ const config = { name: "My Site", url: "https://mysite.com" };
66
89
 
67
- function App({ children }) {
90
+ export default function App({ Component, pageProps }) {
68
91
  return (
69
- <SEOProvider config={siteConfig}>
70
- {children}
92
+ <SEOProvider config={config}>
93
+ <Component {...pageProps} />
71
94
  </SEOProvider>
72
95
  );
73
96
  }
74
97
  ```
75
98
 
76
- ### 2. Specialized Components
77
-
78
- #### News Article
79
99
  ```tsx
80
- import { SeoArticle } from '@shammaa/react-seo';
81
-
82
- <SeoArticle
83
- article={{
84
- title: "Article Title",
85
- description: "Short excerpt...",
86
- image: "/cover.jpg",
87
- publishedAt: "2024-02-01T10:00:00Z",
88
- author: { name: "John Doe" },
89
- category: "Tech"
90
- }}
91
- />
100
+ // pages/article.tsx
101
+ import { SeoArticle } from '@masters-ws/react-seo';
102
+
103
+ export default function ArticlePage({ post }) {
104
+ return (
105
+ <>
106
+ <SeoArticle article={{
107
+ title: post.title,
108
+ description: post.excerpt,
109
+ image: post.cover,
110
+ publishedAt: post.date
111
+ }} />
112
+ <article>...</article>
113
+ </>
114
+ );
115
+ }
92
116
  ```
93
117
 
94
- #### FAQ Page
95
- ```tsx
96
- import { SeoFAQ } from '@shammaa/react-seo';
97
-
98
- <SeoFAQ
99
- questions={[
100
- { q: "Is it free?", a: "Yes, it is!" },
101
- { q: "How to install?", a: "Use npm install." }
102
- ]}
103
- />
118
+ ---
119
+
120
+ ## Core Functions Reference
121
+
122
+ All functions are pure (no side effects) and work in any environment:
123
+
124
+ | Function | Description |
125
+ | :--- | :--- |
126
+ | `toNextMetadata(data, config)` | Converts SEO data to Next.js Metadata object |
127
+ | `generateArticleSchema(data, config)` | Creates NewsArticle JSON-LD |
128
+ | `generateProductSchema(data)` | Creates Product JSON-LD with offers |
129
+ | `generateFAQSchema(questions)` | Creates FAQPage JSON-LD |
130
+ | `generateBreadcrumbSchema(items)` | Creates BreadcrumbList JSON-LD |
131
+ | `generateVideoSchema(data)` | Creates VideoObject JSON-LD |
132
+ | `generateEventSchema(data)` | Creates Event JSON-LD |
133
+ | `generateLocalBusinessSchema(data)` | Creates LocalBusiness JSON-LD |
134
+ | `generatePaginationLinks(url, page, total)` | Returns prev/next/canonical URLs |
135
+ | `generateOrganizationSchema(config)` | Creates Organization JSON-LD |
136
+ | `generateWebSiteSchema(config)` | Creates WebSite JSON-LD with SearchAction |
137
+
138
+ ---
139
+
140
+ ## Components Reference (Requires react-helmet-async)
141
+
142
+ | Component | Description |
143
+ | :--- | :--- |
144
+ | `<SEO />` | Main component for meta tags and schemas |
145
+ | `<SeoArticle />` | For news articles and blog posts |
146
+ | `<SeoProduct />` | For e-commerce products |
147
+ | `<SeoFAQ />` | For FAQ pages |
148
+ | `<SeoVideo />` | For video content |
149
+ | `<SeoEvent />` | For events and conferences |
150
+ | `<SeoLocalBusiness />` | For physical business locations |
151
+ | `<SeoCategory />` | For category pages with pagination |
152
+ | `<SeoTag />` | For tag pages with pagination |
153
+ | `<SeoAuthor />` | For author profile pages |
154
+ | `<Breadcrumb />` | For breadcrumb navigation with schema |
155
+
156
+ ---
157
+
158
+ ## Configuration Options
159
+
160
+ ### SiteConfig
161
+ ```typescript
162
+ {
163
+ name: string; // Site name
164
+ url: string; // Base URL
165
+ logo?: string; // Logo URL
166
+ language?: string; // Default: 'ar'
167
+ twitterHandle?: string; // @username
168
+ themeColor?: string; // Mobile theme color
169
+ }
170
+ ```
171
+
172
+ ### SEOData
173
+ ```typescript
174
+ {
175
+ title?: string;
176
+ description?: string;
177
+ image?: string;
178
+ canonical?: string;
179
+ noindex?: boolean; // Sets robots to noindex
180
+ type?: 'website' | 'article' | 'product';
181
+ alternates?: Array<{ hreflang: string; href: string }>;
182
+ // ... and more
183
+ }
104
184
  ```
105
185
 
106
- ## Detailed Configuration Reference
107
-
108
- ### SiteConfig Props
109
- | Prop | Type | Description |
110
- | :--- | :--- | :--- |
111
- | `name` | `string` | Site name (used in titles and schemas) |
112
- | `url` | `string` | Base URL of the site |
113
- | `logo` | `string` | URL to the site logo |
114
- | `language` | `string` | Main language (e.g., 'ar', 'en') |
115
- | `googleAnalyticsId`| `string` | GA4 Measurement ID |
116
- | `themeColor` | `string` | Browser theme color (mobile) |
117
-
118
- ### SEOData Props (Common)
119
- | Prop | Type | Description |
120
- | :--- | :--- | :--- |
121
- | `title` | `string` | Page title (auto-appends Site name) |
122
- | `noindex` | `boolean` | Sets robots to noindex and removes canonical |
123
- | `alternates` | `Array` | List of `{hreflang, href}` for multilingual |
124
- | `dnsPrefetch` | `string[]` | List of domains to prefetch |
125
- | `readingTime` | `number` | Reading time in minutes for Twitter Cards |
126
-
127
- ## Advanced Schemas List
128
- Use these components for specific SEO needs:
129
- - `<SeoVideo />` - For video content.
130
- - `<SeoEvent />` - For conferences/events.
131
- - `<SeoLocalBusiness />` - For physical stores.
132
- - `<SeoHowTo />` - For tutorials.
133
- - `<SeoReview />` - For product ratings.
134
- - `<SeoJobPosting />` - For hiring.
135
- - `<SeoRecipe />` - For cooking sites.
186
+ ---
136
187
 
137
188
  ## License
138
189
  MIT
@@ -0,0 +1,235 @@
1
+ // src/core/schemas.ts
2
+ function generateOrganizationSchema(config) {
3
+ return {
4
+ "@context": "https://schema.org",
5
+ "@type": "Organization",
6
+ "name": config.name,
7
+ "url": config.url,
8
+ "logo": config.logo,
9
+ "sameAs": config.socialLinks || []
10
+ };
11
+ }
12
+ function generateWebSiteSchema(config) {
13
+ return {
14
+ "@context": "https://schema.org",
15
+ "@type": "WebSite",
16
+ "name": config.name,
17
+ "url": config.url,
18
+ "potentialAction": {
19
+ "@type": "SearchAction",
20
+ "target": `${config.url}/search?q={search_term_string}`,
21
+ "query-input": "required name=search_term_string"
22
+ }
23
+ };
24
+ }
25
+ function generateArticleSchema(data, config) {
26
+ const org = generateOrganizationSchema(config);
27
+ return {
28
+ "@context": "https://schema.org",
29
+ "@type": "NewsArticle",
30
+ "headline": data.title,
31
+ "description": data.description,
32
+ "image": data.image || config.logo,
33
+ "datePublished": data.publishedTime,
34
+ "dateModified": data.modifiedTime || data.publishedTime,
35
+ "mainEntityOfPage": data.url,
36
+ "author": data.author ? {
37
+ "@type": "Person",
38
+ "name": data.author.name,
39
+ "url": data.author.url
40
+ } : org,
41
+ "publisher": org
42
+ };
43
+ }
44
+ function generateProductSchema(data) {
45
+ return {
46
+ "@context": "https://schema.org",
47
+ "@type": "Product",
48
+ "name": data.name,
49
+ "description": data.description,
50
+ "image": data.image,
51
+ "sku": data.sku,
52
+ "brand": data.brand ? { "@type": "Brand", "name": data.brand } : void 0,
53
+ "offers": {
54
+ "@type": "Offer",
55
+ "url": data.url,
56
+ "priceCurrency": data.currency || "USD",
57
+ "price": data.price,
58
+ "availability": data.availability || "https://schema.org/InStock"
59
+ },
60
+ "aggregateRating": data.rating ? {
61
+ "@type": "AggregateRating",
62
+ "ratingValue": data.rating,
63
+ "reviewCount": data.reviewCount || 1
64
+ } : void 0
65
+ };
66
+ }
67
+ function generateFAQSchema(questions) {
68
+ return {
69
+ "@context": "https://schema.org",
70
+ "@type": "FAQPage",
71
+ "mainEntity": questions.map((item) => ({
72
+ "@type": "Question",
73
+ "name": item.q,
74
+ "acceptedAnswer": {
75
+ "@type": "Answer",
76
+ "text": item.a
77
+ }
78
+ }))
79
+ };
80
+ }
81
+ function generateBreadcrumbSchema(items) {
82
+ return {
83
+ "@context": "https://schema.org",
84
+ "@type": "BreadcrumbList",
85
+ "itemListElement": items.map((item, index) => ({
86
+ "@type": "ListItem",
87
+ "position": index + 1,
88
+ "name": item.name,
89
+ "item": item.item
90
+ }))
91
+ };
92
+ }
93
+ function generateVideoSchema(data) {
94
+ return {
95
+ "@context": "https://schema.org",
96
+ "@type": "VideoObject",
97
+ "name": data.name,
98
+ "description": data.description,
99
+ "thumbnailUrl": data.thumbnailUrl,
100
+ "uploadDate": data.uploadDate,
101
+ "duration": data.duration,
102
+ "contentUrl": data.contentUrl,
103
+ "embedUrl": data.embedUrl
104
+ };
105
+ }
106
+ function generateEventSchema(data) {
107
+ const isOnline = data.location && "url" in data.location;
108
+ return {
109
+ "@context": "https://schema.org",
110
+ "@type": "Event",
111
+ "name": data.name,
112
+ "description": data.description,
113
+ "startDate": data.startDate,
114
+ "endDate": data.endDate,
115
+ "eventAttendanceMode": isOnline ? "https://schema.org/OnlineEventAttendanceMode" : "https://schema.org/OfflineEventAttendanceMode",
116
+ "location": isOnline ? {
117
+ "@type": "VirtualLocation",
118
+ "url": data.location.url
119
+ } : data.location ? {
120
+ "@type": "Place",
121
+ "name": data.location.name,
122
+ "address": data.location.address
123
+ } : void 0,
124
+ "image": data.image,
125
+ "offers": data.offers ? {
126
+ "@type": "Offer",
127
+ "price": data.offers.price,
128
+ "priceCurrency": data.offers.currency,
129
+ "url": data.offers.url
130
+ } : void 0
131
+ };
132
+ }
133
+ function generateLocalBusinessSchema(data) {
134
+ return {
135
+ "@context": "https://schema.org",
136
+ "@type": "LocalBusiness",
137
+ "name": data.name,
138
+ "description": data.description,
139
+ "image": data.image,
140
+ "telephone": data.telephone,
141
+ "address": {
142
+ "@type": "PostalAddress",
143
+ "streetAddress": data.address.street,
144
+ "addressLocality": data.address.city,
145
+ "addressRegion": data.address.region,
146
+ "postalCode": data.address.postalCode,
147
+ "addressCountry": data.address.country
148
+ },
149
+ "geo": data.geo ? {
150
+ "@type": "GeoCoordinates",
151
+ "latitude": data.geo.lat,
152
+ "longitude": data.geo.lng
153
+ } : void 0,
154
+ "openingHours": data.openingHours,
155
+ "priceRange": data.priceRange
156
+ };
157
+ }
158
+
159
+ // src/core/metadata.ts
160
+ function toNextMetadata(props, config) {
161
+ const title = props.title ? `${props.title} | ${config.name}` : config.name;
162
+ const description = props.description || config.description;
163
+ const url = props.canonical || config.url;
164
+ const image = props.image || config.logo;
165
+ const metadata = {
166
+ title,
167
+ description,
168
+ keywords: props.keywords,
169
+ robots: props.noindex ? "noindex, nofollow" : props.robots || "index, follow",
170
+ alternates: {
171
+ canonical: props.noindex ? void 0 : url
172
+ },
173
+ openGraph: {
174
+ title: props.ogTitle || title,
175
+ description: props.ogDescription || description,
176
+ url,
177
+ siteName: config.name,
178
+ images: props.ogImage || image ? [{ url: props.ogImage || image }] : [],
179
+ type: props.ogType || (props.type === "article" ? "article" : "website"),
180
+ locale: props.ogLocale || config.language || "ar_SA"
181
+ },
182
+ twitter: {
183
+ card: props.twitterCard || "summary_large_image",
184
+ title: props.twitterTitle || title,
185
+ description: props.twitterDescription || description,
186
+ images: props.twitterImage || image ? [props.twitterImage || image] : [],
187
+ site: config.twitterHandle,
188
+ creator: config.twitterHandle
189
+ },
190
+ other: {}
191
+ };
192
+ if (props.alternates && props.alternates.length > 0) {
193
+ const languages = {};
194
+ props.alternates.forEach((alt) => {
195
+ languages[alt.hreflang] = alt.href;
196
+ });
197
+ metadata.alternates.languages = languages;
198
+ }
199
+ metadata.appleWebApp = {
200
+ capable: true,
201
+ title: config.name,
202
+ statusBarStyle: "default"
203
+ };
204
+ if (config.themeColor) metadata.themeColor = config.themeColor;
205
+ if (config.manifest) metadata.manifest = config.manifest;
206
+ return metadata;
207
+ }
208
+ function generatePaginationLinks(baseUrl, currentPage, totalPages) {
209
+ const hasNext = currentPage < totalPages;
210
+ const hasPrev = currentPage > 1;
211
+ const cleanBase = baseUrl.split("?")[0];
212
+ return {
213
+ next: hasNext ? `${cleanBase}?page=${currentPage + 1}` : void 0,
214
+ prev: hasPrev ? currentPage === 2 ? cleanBase : `${cleanBase}?page=${currentPage - 1}` : void 0,
215
+ canonical: currentPage === 1 ? cleanBase : `${cleanBase}?page=${currentPage}`
216
+ };
217
+ }
218
+ function generatePaginatedTitle(title, page, suffix = "\u0635\u0641\u062D\u0629") {
219
+ return page > 1 ? `${title} - ${suffix} ${page}` : title;
220
+ }
221
+
222
+ export {
223
+ generateOrganizationSchema,
224
+ generateWebSiteSchema,
225
+ generateArticleSchema,
226
+ generateProductSchema,
227
+ generateFAQSchema,
228
+ generateBreadcrumbSchema,
229
+ generateVideoSchema,
230
+ generateEventSchema,
231
+ generateLocalBusinessSchema,
232
+ toNextMetadata,
233
+ generatePaginationLinks,
234
+ generatePaginatedTitle
235
+ };
@@ -0,0 +1 @@
1
+ export { g as generateArticleSchema, b as generateBreadcrumbSchema, c as generateEventSchema, d as generateFAQSchema, e as generateLocalBusinessSchema, f as generateOrganizationSchema, h as generatePaginatedTitle, i as generatePaginationLinks, j as generateProductSchema, k as generateVideoSchema, l as generateWebSiteSchema, t as toNextMetadata } from '../index-CGVLxGDj.mjs';
@@ -0,0 +1 @@
1
+ export { g as generateArticleSchema, b as generateBreadcrumbSchema, c as generateEventSchema, d as generateFAQSchema, e as generateLocalBusinessSchema, f as generateOrganizationSchema, h as generatePaginatedTitle, i as generatePaginationLinks, j as generateProductSchema, k as generateVideoSchema, l as generateWebSiteSchema, t as toNextMetadata } from '../index-CGVLxGDj.js';