react-seo-optimize 1.0.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/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jeremy Inoa (liljemery)
4
+ Email: jeremyinoa67@gmail.com
5
+ GitHub: https://github.com/liljemery
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ 1.⁠ ⁠The above copyright notice and this permission notice shall be included
15
+ in all copies or substantial portions of the Software.
16
+
17
+ 2.⁠ ⁠*Attribution Requirement:* Any application, service, or derivative work
18
+ that makes use of this Software must provide visible and accessible credit
19
+ to the original author, Jeremy Inoa (liljemery), and include a link to the
20
+ original repository: https://github.com/liljemery/fastapi-backend.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,375 @@
1
+ # react-seo-optimize
2
+
3
+ Simple and intuitive SEO component for React with JSON-LD schema generation and HTML injection.
4
+
5
+ ## Features
6
+
7
+ - πŸš€ Simple React component for SEO meta tags
8
+ - πŸ“Š JSON-LD schema generators (Organization, ProfessionalService, Breadcrumb, WebPage)
9
+ - πŸ”§ CLI tool to inject schemas into HTML files
10
+ - 🎯 Open Graph and Twitter Card support
11
+ - πŸ’‘ Intuitive API with smart defaults
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install react-seo-optimize
17
+ # or
18
+ pnpm add react-seo-optimize
19
+ # or
20
+ yarn add react-seo-optimize
21
+ ```
22
+
23
+ **Peer Dependencies:**
24
+
25
+ ```bash
26
+ npm install react react-helmet
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ### 1. Install Dependencies
32
+
33
+ ```bash
34
+ # Install the package
35
+ npm install react-seo-optimize
36
+
37
+ # Install peer dependencies
38
+ npm install react react-helmet
39
+ ```
40
+
41
+ ### 2. Basic Usage in Your React Component
42
+
43
+ ```jsx
44
+ import { SEOptimize } from 'react-seo-optimize';
45
+
46
+ const HomePage = () => {
47
+ return (
48
+ <>
49
+ <SEOptimize
50
+ title="Home | My Company"
51
+ description="Welcome to My Company - We provide amazing services"
52
+ canonical="https://example.com"
53
+ />
54
+
55
+ <h1>Welcome to My Company</h1>
56
+ {/* Your page content */}
57
+ </>
58
+ );
59
+ };
60
+ ```
61
+
62
+ That's it! The component will automatically add all necessary meta tags to your page's `<head>`.
63
+
64
+ ## Tutorial
65
+
66
+ ### Step 1: Basic Page SEO
67
+
68
+ Start by adding the `SEOptimize` component to your pages with essential SEO information:
69
+
70
+ ```jsx
71
+ import { SEOptimize } from 'react-seo-optimize';
72
+
73
+ const AboutPage = () => {
74
+ return (
75
+ <>
76
+ <SEOptimize
77
+ title="About Us | My Company"
78
+ description="Learn more about My Company and our mission to deliver excellence."
79
+ keywords="about us, company, mission"
80
+ canonical="https://example.com/about"
81
+ />
82
+
83
+ <main>
84
+ <h1>About Us</h1>
85
+ <p>Content goes here...</p>
86
+ </main>
87
+ </>
88
+ );
89
+ };
90
+ ```
91
+
92
+ ### Step 2: Add Open Graph Tags for Social Media
93
+
94
+ Enhance your social media sharing with Open Graph tags:
95
+
96
+ ```jsx
97
+ <SEOptimize
98
+ title="Product Page | My Company"
99
+ description="Check out our amazing product"
100
+ canonical="https://example.com/product"
101
+ ogImage="https://example.com/images/product-og.jpg"
102
+ ogImageWidth="1200"
103
+ ogImageHeight="630"
104
+ ogType="product"
105
+ />
106
+ ```
107
+
108
+ ### Step 3: Add Twitter Card Tags
109
+
110
+ Optimize how your links appear on Twitter:
111
+
112
+ ```jsx
113
+ <SEOptimize
114
+ title="Blog Post | My Company"
115
+ description="Read our latest blog post"
116
+ canonical="https://example.com/blog/post"
117
+ twitterCard="summary_large_image"
118
+ twitterImage="https://example.com/images/blog-twitter.jpg"
119
+ />
120
+ ```
121
+
122
+ ### Step 4: Add JSON-LD Schema Markup
123
+
124
+ Add structured data to help search engines understand your content better.
125
+
126
+ #### Organization Schema (Homepage/Global)
127
+
128
+ ```jsx
129
+ import { SEOptimize, generateOrganizationSchema } from 'react-seo-optimize';
130
+
131
+ const HomePage = () => {
132
+ const orgSchema = generateOrganizationSchema({
133
+ name: 'My Company',
134
+ url: 'https://example.com',
135
+ logo: 'https://example.com/logo.png',
136
+ description: 'Leading provider of amazing services',
137
+ sameAs: [
138
+ 'https://facebook.com/mycompany',
139
+ 'https://twitter.com/mycompany',
140
+ 'https://linkedin.com/company/mycompany',
141
+ ],
142
+ });
143
+
144
+ return (
145
+ <>
146
+ <SEOptimize
147
+ title="Home | My Company"
148
+ description="Welcome to My Company"
149
+ canonical="https://example.com"
150
+ schema={orgSchema}
151
+ />
152
+ {/* Content */}
153
+ </>
154
+ );
155
+ };
156
+ ```
157
+
158
+ #### Breadcrumb Schema (Navigation)
159
+
160
+ ```jsx
161
+ import { SEOptimize, generateBreadcrumbSchema } from 'react-seo-optimize';
162
+
163
+ const ProductPage = () => {
164
+ const breadcrumbSchema = generateBreadcrumbSchema([
165
+ { name: 'Home', url: 'https://example.com' },
166
+ { name: 'Products', url: 'https://example.com/products' },
167
+ { name: 'Product Name', url: 'https://example.com/products/123' },
168
+ ]);
169
+
170
+ return (
171
+ <>
172
+ <SEOptimize
173
+ title="Product Name | My Company"
174
+ description="Product description"
175
+ canonical="https://example.com/products/123"
176
+ schema={breadcrumbSchema}
177
+ />
178
+ {/* Product content */}
179
+ </>
180
+ );
181
+ };
182
+ ```
183
+
184
+ #### Professional Service Schema
185
+
186
+ ```jsx
187
+ import { SEOptimize, generateProfessionalServiceSchema } from 'react-seo-optimize';
188
+
189
+ const ServicesPage = () => {
190
+ const serviceSchema = generateProfessionalServiceSchema({
191
+ name: 'Professional Services',
192
+ description: 'We offer professional consulting services',
193
+ url: 'https://example.com/services',
194
+ areaServed: {
195
+ '@type': 'Country',
196
+ name: 'United States',
197
+ },
198
+ serviceType: 'Consulting',
199
+ });
200
+
201
+ return (
202
+ <>
203
+ <SEOptimize
204
+ title="Our Services | My Company"
205
+ description="Professional services we offer"
206
+ canonical="https://example.com/services"
207
+ schema={serviceSchema}
208
+ />
209
+ {/* Services content */}
210
+ </>
211
+ );
212
+ };
213
+ ```
214
+
215
+ ### Step 5: Use CLI to Inject Global Schema
216
+
217
+ For static HTML files or global organization schema, use the CLI tool:
218
+
219
+ 1. Create `schema.config.json` in your project root:
220
+
221
+ ```json
222
+ {
223
+ "name": "My Organization",
224
+ "url": "https://example.com",
225
+ "logo": "https://example.com/logo.svg",
226
+ "description": "Organization description",
227
+ "address": {
228
+ "@type": "PostalAddress",
229
+ "addressCountry": "US",
230
+ "addressLocality": "City"
231
+ },
232
+ "sameAs": [
233
+ "https://facebook.com/myorg",
234
+ "https://twitter.com/myorg"
235
+ ]
236
+ }
237
+ ```
238
+
239
+ 2. Run the CLI command:
240
+
241
+ ```bash
242
+ npx react-seo-generate-schema
243
+ ```
244
+
245
+ Or add to your `package.json` scripts:
246
+
247
+ ```json
248
+ {
249
+ "scripts": {
250
+ "generate:schema": "react-seo-generate-schema"
251
+ }
252
+ }
253
+ ```
254
+
255
+ Then run:
256
+
257
+ ```bash
258
+ npm run generate:schema
259
+ ```
260
+
261
+ This will automatically inject the JSON-LD schema into your `index.html` file.
262
+
263
+ ## Complete Example
264
+
265
+ Here's a complete example combining multiple features:
266
+
267
+ ```jsx
268
+ import { SEOptimize, generateOrganizationSchema } from 'react-seo-optimize';
269
+
270
+ const ArticlePage = ({ article }) => {
271
+ // Generate schema for the organization
272
+ const orgSchema = generateOrganizationSchema({
273
+ name: 'My Company',
274
+ url: 'https://example.com',
275
+ logo: 'https://example.com/logo.png',
276
+ });
277
+
278
+ return (
279
+ <>
280
+ <SEOptimize
281
+ title={`${article.title} | My Company Blog`}
282
+ description={article.excerpt}
283
+ keywords={article.tags.join(', ')}
284
+ canonical={`https://example.com/blog/${article.slug}`}
285
+ ogTitle={article.title}
286
+ ogDescription={article.excerpt}
287
+ ogImage={article.featuredImage}
288
+ ogType="article"
289
+ ogImageWidth="1200"
290
+ ogImageHeight="630"
291
+ twitterCard="summary_large_image"
292
+ twitterImage={article.featuredImage}
293
+ robots={article.published ? 'index, follow' : 'noindex, nofollow'}
294
+ author={article.author.name}
295
+ schema={orgSchema}
296
+ />
297
+
298
+ <article>
299
+ <h1>{article.title}</h1>
300
+ <div dangerouslySetInnerHTML={{ __html: article.content }} />
301
+ </article>
302
+ </>
303
+ );
304
+ };
305
+ ```
306
+
307
+ ## Advanced Usage
308
+
309
+ ### Schema Generators Reference
310
+
311
+ All available schema generators:
312
+
313
+ ```jsx
314
+ import {
315
+ generateOrganizationSchema,
316
+ generateProfessionalServiceSchema,
317
+ generateBreadcrumbSchema,
318
+ generateWebPageSchema
319
+ } from 'react-seo-optimize';
320
+
321
+ // Organization schema
322
+ const orgSchema = generateOrganizationSchema({
323
+ name: 'My Company',
324
+ url: 'https://example.com',
325
+ logo: 'https://example.com/logo.svg',
326
+ description: 'Company description',
327
+ sameAs: [
328
+ 'https://facebook.com/mycompany',
329
+ 'https://twitter.com/mycompany',
330
+ ],
331
+ });
332
+
333
+ // WebPage schema
334
+ const webPageSchema = generateWebPageSchema({
335
+ name: 'Page Title',
336
+ description: 'Page description',
337
+ url: 'https://example.com/page',
338
+ inLanguage: 'en',
339
+ });
340
+ ```
341
+
342
+ ## Component Props
343
+
344
+ | Prop | Type | Default | Description |
345
+ |------|------|---------|-------------|
346
+ | `title` | string | - | Page title |
347
+ | `description` | string | - | Meta description |
348
+ | `keywords` | string | - | Meta keywords |
349
+ | `canonical` | string | - | Canonical URL |
350
+ | `ogTitle` | string | `title` | Open Graph title |
351
+ | `ogDescription` | string | `description` | Open Graph description |
352
+ | `ogUrl` | string | `canonical` | Open Graph URL |
353
+ | `ogImage` | string | - | Open Graph image |
354
+ | `ogType` | string | `'website'` | Open Graph type |
355
+ | `ogImageWidth` | string | - | Open Graph image width |
356
+ | `ogImageHeight` | string | - | Open Graph image height |
357
+ | `ogImageAlt` | string | - | Open Graph image alt |
358
+ | `ogSiteName` | string | - | Open Graph site name |
359
+ | `ogLocale` | string | - | Open Graph locale |
360
+ | `twitterCard` | string | `'summary_large_image'` | Twitter card type |
361
+ | `twitterTitle` | string | `title` | Twitter title |
362
+ | `twitterDescription` | string | `description` | Twitter description |
363
+ | `twitterImage` | string | `ogImage` | Twitter image |
364
+ | `schema` | object | - | JSON-LD schema object |
365
+ | `robots` | string | `'index, follow'` | Robots meta tag |
366
+ | `author` | string | - | Author meta tag |
367
+ | `...extraMeta` | object | - | Additional meta tags |
368
+
369
+ ## Examples
370
+
371
+ See `EXAMPLE_USAGE.jsx` in the package for more examples.
372
+
373
+ ## License
374
+
375
+ MIT
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ const generateOrganizationSchema = (config) => {
11
+ const schema = {
12
+ '@context': 'https://schema.org',
13
+ '@type': 'Organization',
14
+ name: config.name,
15
+ ...(config.alternateName && { alternateName: config.alternateName }),
16
+ ...(config.url && { url: config.url }),
17
+ ...(config.logo && { logo: config.logo }),
18
+ ...(config.description && { description: config.description }),
19
+ ...(config.address && { address: config.address }),
20
+ ...(config.contactPoint && { contactPoint: config.contactPoint }),
21
+ ...(config.sameAs && config.sameAs.length > 0 && { sameAs: config.sameAs }),
22
+ ...(config.areaServed && { areaServed: config.areaServed }),
23
+ ...(config.hasOfferCatalog && { hasOfferCatalog: config.hasOfferCatalog }),
24
+ };
25
+
26
+ return schema;
27
+ };
28
+
29
+ const getProjectRoot = () => {
30
+ let currentDir = process.cwd();
31
+ let indexHtmlPath = path.join(currentDir, 'index.html');
32
+
33
+ while (!fs.existsSync(indexHtmlPath) && currentDir !== path.dirname(currentDir)) {
34
+ currentDir = path.dirname(currentDir);
35
+ indexHtmlPath = path.join(currentDir, 'index.html');
36
+ }
37
+
38
+ return fs.existsSync(indexHtmlPath) ? currentDir : process.cwd();
39
+ };
40
+
41
+ const configPath = process.argv[2] || path.join(getProjectRoot(), 'schema.config.json');
42
+ const indexHtmlPath = process.argv[3] || path.join(getProjectRoot(), 'index.html');
43
+
44
+ let config = {};
45
+
46
+ if (fs.existsSync(configPath)) {
47
+ try {
48
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
49
+ } catch (error) {
50
+ console.error(`❌ Error reading config file: ${error.message}`);
51
+ process.exit(1);
52
+ }
53
+ } else {
54
+ console.log(`⚠️ Config file not found at ${configPath}`);
55
+ console.log('Please create a schema.config.json file with your organization details.');
56
+ console.log('Example:');
57
+ console.log(JSON.stringify({
58
+ name: 'Your Organization',
59
+ url: 'https://example.com',
60
+ description: 'Your description',
61
+ }, null, 2));
62
+ process.exit(1);
63
+ }
64
+
65
+ if (!config.name) {
66
+ console.error('❌ "name" is required in schema.config.json');
67
+ process.exit(1);
68
+ }
69
+
70
+ const schema = generateOrganizationSchema(config);
71
+
72
+ try {
73
+ if (!fs.existsSync(indexHtmlPath)) {
74
+ console.error(`❌ index.html not found at ${indexHtmlPath}`);
75
+ process.exit(1);
76
+ }
77
+
78
+ let htmlContent = fs.readFileSync(indexHtmlPath, 'utf-8');
79
+
80
+ const schemaJsonString = JSON.stringify(schema, null, 2);
81
+ const indentedSchema = schemaJsonString
82
+ .split('\n')
83
+ .map((line) => ` ${line}`)
84
+ .join('\n');
85
+
86
+ const schemaScript = ` <!-- Structured Data for Search Engines -->
87
+ <script type="application/ld+json">
88
+ ${indentedSchema}
89
+ </script>`;
90
+
91
+ const scriptRegex = / <!-- Structured Data for Search Engines -->\s*<script type="application\/ld\+json">[\s\S]*?<\/script>/;
92
+
93
+ if (scriptRegex.test(htmlContent)) {
94
+ htmlContent = htmlContent.replace(scriptRegex, schemaScript);
95
+ console.log('βœ… Schema updated in index.html');
96
+ } else {
97
+ const headEndRegex = /(<\/head>)/;
98
+ if (headEndRegex.test(htmlContent)) {
99
+ htmlContent = htmlContent.replace(headEndRegex, `${schemaScript}\n $1`);
100
+ console.log('βœ… Schema added to index.html');
101
+ } else {
102
+ console.error('❌ Could not find </head> tag in index.html');
103
+ process.exit(1);
104
+ }
105
+ }
106
+
107
+ fs.writeFileSync(indexHtmlPath, htmlContent, 'utf-8');
108
+ console.log(`βœ… Schema successfully written to ${indexHtmlPath}`);
109
+
110
+ } catch (error) {
111
+ console.error(`❌ Error updating index.html: ${error.message}`);
112
+ process.exit(1);
113
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "react-seo-optimize",
3
+ "version": "1.0.0",
4
+ "description": "Simple and intuitive SEO component for React with JSON-LD schema generation and HTML injection",
5
+ "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "type": "module",
8
+ "scripts": {
9
+ "generate:schema": "node bin/generate-schema.js",
10
+ "test": "vitest",
11
+ "test:ui": "vitest --ui",
12
+ "test:coverage": "vitest --coverage"
13
+ },
14
+ "bin": {
15
+ "react-seo-generate-schema": "./bin/generate-schema.js"
16
+ },
17
+ "keywords": [
18
+ "react",
19
+ "seo",
20
+ "schema.org",
21
+ "json-ld",
22
+ "meta-tags",
23
+ "open-graph",
24
+ "twitter-cards",
25
+ "react-helmet"
26
+ ],
27
+ "author": "Jeremy Inoa Fortuna",
28
+ "email": "",
29
+ "url": "https://github.com/liljemery/react-seo-optimize.git",
30
+ "homepage": "https://github.com/liljemery/react-seo-optimize.git",
31
+ "license": "MIT",
32
+ "peerDependencies": {
33
+ "react": ">=16.8.0",
34
+ "react-helmet": "^6.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@testing-library/react": "^14.1.2",
38
+ "@testing-library/jest-dom": "^6.1.5",
39
+ "@vitest/ui": "^1.1.3",
40
+ "jsdom": "^23.0.1",
41
+ "react": "^18.2.0",
42
+ "react-helmet": "^6.1.0",
43
+ "vitest": "^1.1.3"
44
+ },
45
+ "files": [
46
+ "src/",
47
+ "bin/",
48
+ "README.md",
49
+ "LICENSE"
50
+ ],
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/liljemery/react-seo-optimize.git"
54
+ }
55
+ }
@@ -0,0 +1,72 @@
1
+ import React, { memo } from 'react';
2
+ import { Helmet } from 'react-helmet';
3
+
4
+ const SEOptimize = ({
5
+ title,
6
+ description,
7
+ keywords,
8
+ canonical,
9
+ ogTitle,
10
+ ogDescription,
11
+ ogUrl,
12
+ ogImage,
13
+ ogType = 'website',
14
+ ogImageWidth,
15
+ ogImageHeight,
16
+ ogImageAlt,
17
+ ogSiteName,
18
+ ogLocale,
19
+ twitterCard = 'summary_large_image',
20
+ twitterTitle,
21
+ twitterDescription,
22
+ twitterImage,
23
+ twitterImageAlt,
24
+ schema,
25
+ robots = 'index, follow',
26
+ author,
27
+ ...extraMeta
28
+ }) => {
29
+ const finalOgTitle = ogTitle || title;
30
+ const finalOgDescription = ogDescription || description;
31
+ const finalOgUrl = ogUrl || canonical;
32
+ const finalTwitterTitle = twitterTitle || title;
33
+ const finalTwitterDescription = twitterDescription || description;
34
+ const finalTwitterImage = twitterImage || ogImage;
35
+
36
+ return (
37
+ <Helmet>
38
+ {title && <title>{title}</title>}
39
+ {description && <meta name="description" content={description} />}
40
+ {keywords && <meta name="keywords" content={keywords} />}
41
+ {canonical && <link rel="canonical" href={canonical} />}
42
+ {robots && <meta name="robots" content={robots} />}
43
+ {author && <meta name="author" content={author} />}
44
+
45
+ {finalOgTitle && <meta property="og:title" content={finalOgTitle} />}
46
+ {finalOgDescription && <meta property="og:description" content={finalOgDescription} />}
47
+ {finalOgUrl && <meta property="og:url" content={finalOgUrl} />}
48
+ {ogType && <meta property="og:type" content={ogType} />}
49
+ {ogImage && <meta property="og:image" content={ogImage} />}
50
+ {ogImageWidth && <meta property="og:image:width" content={ogImageWidth} />}
51
+ {ogImageHeight && <meta property="og:image:height" content={ogImageHeight} />}
52
+ {ogImageAlt && <meta property="og:image:alt" content={ogImageAlt} />}
53
+ {ogSiteName && <meta property="og:site_name" content={ogSiteName} />}
54
+ {ogLocale && <meta property="og:locale" content={ogLocale} />}
55
+
56
+ {twitterCard && <meta name="twitter:card" content={twitterCard} />}
57
+ {finalTwitterTitle && <meta name="twitter:title" content={finalTwitterTitle} />}
58
+ {finalTwitterDescription && <meta name="twitter:description" content={finalTwitterDescription} />}
59
+ {finalOgUrl && <meta name="twitter:url" content={finalOgUrl} />}
60
+ {finalTwitterImage && <meta name="twitter:image" content={finalTwitterImage} />}
61
+ {twitterImageAlt && <meta name="twitter:image:alt" content={twitterImageAlt} />}
62
+
63
+ {schema && <script type="application/ld+json">{JSON.stringify(schema)}</script>}
64
+
65
+ {Object.entries(extraMeta).map(([key, value]) => (
66
+ <meta key={key} name={key} content={value} />
67
+ ))}
68
+ </Helmet>
69
+ );
70
+ };
71
+
72
+ export default memo(SEOptimize);
package/src/index.d.ts ADDED
@@ -0,0 +1,80 @@
1
+ import { ComponentType } from 'react';
2
+
3
+ export interface SEOptimizeProps {
4
+ title?: string;
5
+ description?: string;
6
+ keywords?: string;
7
+ canonical?: string;
8
+ ogTitle?: string;
9
+ ogDescription?: string;
10
+ ogUrl?: string;
11
+ ogImage?: string;
12
+ ogType?: string;
13
+ ogImageWidth?: string;
14
+ ogImageHeight?: string;
15
+ ogImageAlt?: string;
16
+ ogSiteName?: string;
17
+ ogLocale?: string;
18
+ twitterCard?: string;
19
+ twitterTitle?: string;
20
+ twitterDescription?: string;
21
+ twitterImage?: string;
22
+ twitterImageAlt?: string;
23
+ schema?: object;
24
+ robots?: string;
25
+ author?: string;
26
+ [key: string]: any;
27
+ }
28
+
29
+ export declare const SEOptimize: ComponentType<SEOptimizeProps>;
30
+
31
+ export interface OrganizationSchemaConfig {
32
+ name: string;
33
+ alternateName?: string;
34
+ url?: string;
35
+ logo?: string;
36
+ description?: string;
37
+ address?: object;
38
+ contactPoint?: object;
39
+ sameAs?: string[];
40
+ areaServed?: object;
41
+ hasOfferCatalog?: object;
42
+ }
43
+
44
+ export interface ProfessionalServiceSchemaConfig {
45
+ name?: string;
46
+ description?: string;
47
+ url?: string;
48
+ areaServed?: object;
49
+ serviceType?: string;
50
+ provider?: object;
51
+ }
52
+
53
+ export interface BreadcrumbItem {
54
+ name: string;
55
+ url: string;
56
+ }
57
+
58
+ export interface WebPageSchemaConfig {
59
+ name?: string;
60
+ description?: string;
61
+ url?: string;
62
+ inLanguage?: string;
63
+ isPartOf?: object;
64
+ }
65
+
66
+ export declare function generateOrganizationSchema(
67
+ config: OrganizationSchemaConfig
68
+ ): object;
69
+
70
+ export declare function generateProfessionalServiceSchema(
71
+ config: ProfessionalServiceSchemaConfig
72
+ ): object;
73
+
74
+ export declare function generateBreadcrumbSchema(
75
+ items: BreadcrumbItem[]
76
+ ): object;
77
+
78
+ export declare function generateWebPageSchema(
79
+ config: WebPageSchemaConfig
80
+ ): object;
package/src/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { default as SEOptimize } from './SEOptimize.jsx';
2
+ export * from './schemaGenerators.js';
@@ -0,0 +1,115 @@
1
+ import { validateRequired, validateUrl, validateArray } from './utils/validation.js';
2
+
3
+ export const generateOrganizationSchema = ({
4
+ name,
5
+ alternateName,
6
+ url,
7
+ logo,
8
+ description,
9
+ address,
10
+ contactPoint,
11
+ sameAs = [],
12
+ areaServed,
13
+ hasOfferCatalog,
14
+ }) => {
15
+ validateRequired(name, 'name');
16
+
17
+ if (url) validateUrl(url, 'url');
18
+ if (logo) validateUrl(logo, 'logo');
19
+ validateArray(sameAs, 'sameAs');
20
+
21
+ const schema = {
22
+ '@context': 'https://schema.org',
23
+ '@type': 'Organization',
24
+ name,
25
+ ...(alternateName && { alternateName }),
26
+ ...(url && { url }),
27
+ ...(logo && { logo }),
28
+ ...(description && { description }),
29
+ ...(address && { address }),
30
+ ...(contactPoint && { contactPoint }),
31
+ ...(sameAs.length > 0 && { sameAs }),
32
+ ...(areaServed && { areaServed }),
33
+ ...(hasOfferCatalog && { hasOfferCatalog }),
34
+ };
35
+
36
+ return schema;
37
+ };
38
+
39
+ export const generateProfessionalServiceSchema = ({
40
+ name,
41
+ description,
42
+ url,
43
+ areaServed,
44
+ serviceType,
45
+ provider,
46
+ }) => {
47
+ if (url) validateUrl(url, 'url');
48
+
49
+ const schema = {
50
+ '@context': 'https://schema.org',
51
+ '@type': 'ProfessionalService',
52
+ ...(name && { name }),
53
+ ...(description && { description }),
54
+ ...(url && { url }),
55
+ ...(areaServed && { areaServed }),
56
+ ...(serviceType && { serviceType }),
57
+ ...(provider && { provider }),
58
+ };
59
+
60
+ return schema;
61
+ };
62
+
63
+ export const generateBreadcrumbSchema = (items) => {
64
+ validateRequired(items, 'items');
65
+ validateArray(items, 'items');
66
+
67
+ if (items.length === 0) {
68
+ throw new Error('Breadcrumb items array cannot be empty.');
69
+ }
70
+
71
+ items.forEach((item, index) => {
72
+ if (!item.name) {
73
+ throw new Error(`Breadcrumb item at index ${index} is missing required field "name".`);
74
+ }
75
+ if (!item.url) {
76
+ throw new Error(`Breadcrumb item at index ${index} is missing required field "url".`);
77
+ }
78
+ validateUrl(item.url, `items[${index}].url`);
79
+ });
80
+
81
+ const schema = {
82
+ '@context': 'https://schema.org',
83
+ '@type': 'BreadcrumbList',
84
+ itemListElement: items.map((item, index) => ({
85
+ '@type': 'ListItem',
86
+ position: index + 1,
87
+ name: item.name,
88
+ item: item.url,
89
+ })),
90
+ };
91
+
92
+ return schema;
93
+ };
94
+
95
+ export const generateWebPageSchema = ({
96
+ name,
97
+ description,
98
+ url,
99
+ inLanguage = 'es',
100
+ isPartOf,
101
+ }) => {
102
+ if (url) validateUrl(url, 'url');
103
+
104
+ const schema = {
105
+ '@context': 'https://schema.org',
106
+ '@type': 'WebPage',
107
+ ...(name && { name }),
108
+ ...(description && { description }),
109
+ ...(url && { url }),
110
+ ...(inLanguage && { inLanguage }),
111
+ ...(isPartOf && { isPartOf }),
112
+ };
113
+
114
+ return schema;
115
+ };
@@ -0,0 +1,30 @@
1
+ export const isValidUrl = (url) => {
2
+ if (typeof url !== 'string') return false;
3
+ try {
4
+ new URL(url);
5
+ return true;
6
+ } catch {
7
+ return false;
8
+ }
9
+ };
10
+
11
+ export const validateUrl = (url, fieldName = 'url') => {
12
+ if (url && !isValidUrl(url)) {
13
+ throw new Error(`Invalid ${fieldName}: "${url}". Must be a valid URL.`);
14
+ }
15
+ return true;
16
+ };
17
+
18
+ export const validateRequired = (value, fieldName) => {
19
+ if (!value) {
20
+ throw new Error(`Required field "${fieldName}" is missing.`);
21
+ }
22
+ return true;
23
+ };
24
+
25
+ export const validateArray = (value, fieldName) => {
26
+ if (value && !Array.isArray(value)) {
27
+ throw new Error(`"${fieldName}" must be an array.`);
28
+ }
29
+ return true;
30
+ };