smart-seo-lite 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 +21 -0
- package/README.md +174 -0
- package/dist/SEOImg.d.ts +23 -0
- package/dist/Schema.d.ts +71 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.esm.js +276 -0
- package/dist/index.js +280 -0
- package/dist/useSEO.d.ts +28 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 virendra2902
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# 🚀 smart-seo-lite
|
|
2
|
+
|
|
3
|
+
> **"Add SEO to your React/Next.js app in 3 lines of code."**
|
|
4
|
+
|
|
5
|
+
[](https://npmjs.com/package/smart-seo-lite)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://bundlephobia.com/package/smart-seo-lite)
|
|
8
|
+
|
|
9
|
+
A lightweight SEO utility for React + Next.js that covers **80% of SEO needs** with zero configuration.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ✨ Features
|
|
14
|
+
|
|
15
|
+
| Feature | What it does |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `useSEO()` | Auto meta tags, Open Graph, Twitter Cards, canonical URL |
|
|
18
|
+
| `<Schema />` | JSON-LD structured data for articles, products, FAQs & more |
|
|
19
|
+
| `<SEOImg />` | Smart image SEO with auto-alt, lazy loading & dev warnings |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 📦 Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install smart-seo-lite
|
|
27
|
+
# or
|
|
28
|
+
yarn add smart-seo-lite
|
|
29
|
+
# or
|
|
30
|
+
pnpm add smart-seo-lite
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 🔧 Usage
|
|
36
|
+
|
|
37
|
+
### 1. `useSEO` Hook
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { useSEO } from "smart-seo-lite";
|
|
41
|
+
|
|
42
|
+
function HomePage() {
|
|
43
|
+
useSEO({
|
|
44
|
+
title: "Home Page | My Shop",
|
|
45
|
+
description: "Best products online",
|
|
46
|
+
image: "/banner.png",
|
|
47
|
+
type: "website",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return <main>...</main>;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**What it sets automatically:**
|
|
55
|
+
- `<title>` tag
|
|
56
|
+
- `<meta name="description">`
|
|
57
|
+
- Open Graph: `og:title`, `og:description`, `og:image`, `og:url`, `og:type`
|
|
58
|
+
- Twitter: `twitter:card`, `twitter:title`, `twitter:description`, `twitter:image`
|
|
59
|
+
- `<link rel="canonical">`
|
|
60
|
+
- `<meta name="robots">`
|
|
61
|
+
|
|
62
|
+
**All options:**
|
|
63
|
+
```ts
|
|
64
|
+
useSEO({
|
|
65
|
+
title?: string; // Page title
|
|
66
|
+
description?: string; // Meta description
|
|
67
|
+
image?: string; // OG/Twitter image URL
|
|
68
|
+
url?: string; // Canonical URL (auto-detects if not set)
|
|
69
|
+
siteName?: string; // OG site name
|
|
70
|
+
type?: "website" | "article" | "product";
|
|
71
|
+
twitterHandle?: string; // e.g. "@myapp"
|
|
72
|
+
noIndex?: boolean; // Set true to noindex
|
|
73
|
+
canonical?: string; // Override canonical URL
|
|
74
|
+
keywords?: string[]; // Meta keywords array
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### 2. `<Schema />` Component
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import { Schema } from "smart-seo-lite";
|
|
84
|
+
|
|
85
|
+
// Article
|
|
86
|
+
<Schema type="article" data={{
|
|
87
|
+
title: "My Blog Post",
|
|
88
|
+
author: "Virendra",
|
|
89
|
+
datePublished: "2025-01-01",
|
|
90
|
+
description: "An amazing post",
|
|
91
|
+
}} />
|
|
92
|
+
|
|
93
|
+
// Product
|
|
94
|
+
<Schema type="product" data={{
|
|
95
|
+
name: "Running Shoes",
|
|
96
|
+
price: 99.99,
|
|
97
|
+
currency: "USD",
|
|
98
|
+
availability: "InStock",
|
|
99
|
+
brand: "Nike",
|
|
100
|
+
}} />
|
|
101
|
+
|
|
102
|
+
// FAQ
|
|
103
|
+
<Schema type="faq" data={{
|
|
104
|
+
questions: [
|
|
105
|
+
{ question: "What is this?", answer: "A great SEO library." },
|
|
106
|
+
{ question: "Is it free?", answer: "Yes, MIT licensed!" },
|
|
107
|
+
]
|
|
108
|
+
}} />
|
|
109
|
+
|
|
110
|
+
// Breadcrumb
|
|
111
|
+
<Schema type="breadcrumb" data={{
|
|
112
|
+
items: [
|
|
113
|
+
{ name: "Home", url: "https://myapp.com" },
|
|
114
|
+
{ name: "Blog", url: "https://myapp.com/blog" },
|
|
115
|
+
{ name: "Post Title", url: "https://myapp.com/blog/post" },
|
|
116
|
+
]
|
|
117
|
+
}} />
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Supported schema types:** `article`, `product`, `organization`, `breadcrumb`, `faq`, `person`, `website`
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### 3. `<SEOImg />` Component
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
import { SEOImg } from "smart-seo-lite";
|
|
128
|
+
|
|
129
|
+
// Auto-generates alt from title
|
|
130
|
+
<SEOImg src="/product.jpg" title="Running Shoes" />
|
|
131
|
+
|
|
132
|
+
// Or provide explicit alt
|
|
133
|
+
<SEOImg src="/hero.jpg" alt="Hero banner for summer sale" width={1200} height={600} />
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**What it does automatically:**
|
|
137
|
+
- Generates `alt` from `title` or filename if not provided
|
|
138
|
+
- Adds `loading="lazy"` by default
|
|
139
|
+
- Warns in development if `src` is missing
|
|
140
|
+
- Warns in development if no `alt`/`title` provided
|
|
141
|
+
- Warns if `width`/`height` missing (CLS prevention)
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## ⚙️ Next.js Usage
|
|
146
|
+
|
|
147
|
+
Works with both App Router and Pages Router. For SSR with `next/head`, use the `useSEO` hook inside your page components — it runs on the client side and updates the head.
|
|
148
|
+
|
|
149
|
+
For SSR-first meta tags in Next.js App Router, combine with `generateMetadata()`:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
// app/page.tsx
|
|
153
|
+
export const metadata = {
|
|
154
|
+
title: "My Page",
|
|
155
|
+
description: "My description",
|
|
156
|
+
};
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
And use `useSEO` for dynamic updates (after user interactions, filtering, etc.).
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 🎯 Philosophy
|
|
164
|
+
|
|
165
|
+
- **Zero dependencies** (just React as a peer dep)
|
|
166
|
+
- **Tiny bundle** — < 3KB gzipped
|
|
167
|
+
- **Drop-in** — no providers, no wrappers
|
|
168
|
+
- **Dev-friendly** — helpful console warnings during development
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 📄 License
|
|
173
|
+
|
|
174
|
+
MIT © Virendra
|
package/dist/SEOImg.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React, { ImgHTMLAttributes } from "react";
|
|
2
|
+
interface SEOImgProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, "alt" | "loading"> {
|
|
3
|
+
src: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
alt?: string;
|
|
6
|
+
loading?: "lazy" | "eager" | "auto";
|
|
7
|
+
width?: number;
|
|
8
|
+
height?: number;
|
|
9
|
+
className?: string;
|
|
10
|
+
style?: React.CSSProperties;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* SEOImg - Smart image component for SEO
|
|
14
|
+
* ✅ Auto-generates alt text from title or filename
|
|
15
|
+
* ✅ Adds loading="lazy" by default
|
|
16
|
+
* ✅ Warns in development if src is missing
|
|
17
|
+
* ✅ Adds width/height to prevent CLS
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* <SEOImg src="/product.jpg" title="Running Shoes" />
|
|
21
|
+
*/
|
|
22
|
+
export declare function SEOImg({ src, title, alt, loading, width, height, className, style, ...rest }: SEOImgProps): React.JSX.Element | null;
|
|
23
|
+
export {};
|
package/dist/Schema.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
type SchemaType = "article" | "product" | "organization" | "breadcrumb" | "faq" | "person" | "website";
|
|
3
|
+
interface ArticleData {
|
|
4
|
+
title: string;
|
|
5
|
+
author?: string;
|
|
6
|
+
datePublished?: string;
|
|
7
|
+
dateModified?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
image?: string;
|
|
10
|
+
url?: string;
|
|
11
|
+
}
|
|
12
|
+
interface ProductData {
|
|
13
|
+
name: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
image?: string;
|
|
16
|
+
price?: number;
|
|
17
|
+
currency?: string;
|
|
18
|
+
availability?: "InStock" | "OutOfStock" | "PreOrder";
|
|
19
|
+
brand?: string;
|
|
20
|
+
sku?: string;
|
|
21
|
+
}
|
|
22
|
+
interface OrganizationData {
|
|
23
|
+
name: string;
|
|
24
|
+
url?: string;
|
|
25
|
+
logo?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
email?: string;
|
|
28
|
+
phone?: string;
|
|
29
|
+
address?: string;
|
|
30
|
+
}
|
|
31
|
+
interface BreadcrumbData {
|
|
32
|
+
items: Array<{
|
|
33
|
+
name: string;
|
|
34
|
+
url: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
interface FAQData {
|
|
38
|
+
questions: Array<{
|
|
39
|
+
question: string;
|
|
40
|
+
answer: string;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
interface PersonData {
|
|
44
|
+
name: string;
|
|
45
|
+
url?: string;
|
|
46
|
+
image?: string;
|
|
47
|
+
jobTitle?: string;
|
|
48
|
+
description?: string;
|
|
49
|
+
}
|
|
50
|
+
interface WebsiteData {
|
|
51
|
+
name: string;
|
|
52
|
+
url?: string;
|
|
53
|
+
description?: string;
|
|
54
|
+
}
|
|
55
|
+
type SchemaData = ArticleData | ProductData | OrganizationData | BreadcrumbData | FAQData | PersonData | WebsiteData;
|
|
56
|
+
interface SchemaProps {
|
|
57
|
+
type: SchemaType;
|
|
58
|
+
data: SchemaData;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Schema - Generates structured JSON-LD data for SEO
|
|
62
|
+
* Supports: article, product, organization, breadcrumb, faq, person, website
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* <Schema type="article" data={{
|
|
66
|
+
* title: "Blog Title",
|
|
67
|
+
* author: "Virendra"
|
|
68
|
+
* }} />
|
|
69
|
+
*/
|
|
70
|
+
export declare function Schema({ type, data }: SchemaProps): React.JSX.Element;
|
|
71
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
function setMeta(name, content, attr = "name") {
|
|
4
|
+
if (typeof document === "undefined")
|
|
5
|
+
return;
|
|
6
|
+
let el = document.querySelector(`meta[${attr}="${name}"]`);
|
|
7
|
+
if (!el) {
|
|
8
|
+
el = document.createElement("meta");
|
|
9
|
+
el.setAttribute(attr, name);
|
|
10
|
+
document.head.appendChild(el);
|
|
11
|
+
}
|
|
12
|
+
el.setAttribute("content", content);
|
|
13
|
+
}
|
|
14
|
+
function setLink(rel, href) {
|
|
15
|
+
if (typeof document === "undefined")
|
|
16
|
+
return;
|
|
17
|
+
let el = document.querySelector(`link[rel="${rel}"]`);
|
|
18
|
+
if (!el) {
|
|
19
|
+
el = document.createElement("link");
|
|
20
|
+
el.setAttribute("rel", rel);
|
|
21
|
+
document.head.appendChild(el);
|
|
22
|
+
}
|
|
23
|
+
el.setAttribute("href", href);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* useSEO - Automatically handles:
|
|
27
|
+
* ✅ title, description
|
|
28
|
+
* ✅ Open Graph tags
|
|
29
|
+
* ✅ Twitter Card tags
|
|
30
|
+
* ✅ Canonical URL
|
|
31
|
+
* ✅ Keywords, robots
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* useSEO({
|
|
35
|
+
* title: "Home Page",
|
|
36
|
+
* description: "Best products online",
|
|
37
|
+
* image: "/banner.png"
|
|
38
|
+
* });
|
|
39
|
+
*/
|
|
40
|
+
function useSEO({ title, description, image, url, siteName = "My App", type = "website", twitterHandle, noIndex = false, canonical, keywords = [], }) {
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (typeof document === "undefined")
|
|
43
|
+
return;
|
|
44
|
+
const resolvedURL = url || (typeof window !== "undefined" ? window.location.href : "");
|
|
45
|
+
const canonicalURL = canonical || resolvedURL;
|
|
46
|
+
// --- Basic Meta ---
|
|
47
|
+
if (title)
|
|
48
|
+
document.title = title;
|
|
49
|
+
if (description)
|
|
50
|
+
setMeta("description", description);
|
|
51
|
+
if (keywords.length > 0)
|
|
52
|
+
setMeta("keywords", keywords.join(", "));
|
|
53
|
+
setMeta("robots", noIndex ? "noindex,nofollow" : "index,follow");
|
|
54
|
+
// --- Open Graph ---
|
|
55
|
+
if (title)
|
|
56
|
+
setMeta("og:title", title, "property");
|
|
57
|
+
if (description)
|
|
58
|
+
setMeta("og:description", description, "property");
|
|
59
|
+
if (image)
|
|
60
|
+
setMeta("og:image", image, "property");
|
|
61
|
+
if (resolvedURL)
|
|
62
|
+
setMeta("og:url", resolvedURL, "property");
|
|
63
|
+
setMeta("og:type", type, "property");
|
|
64
|
+
setMeta("og:site_name", siteName, "property");
|
|
65
|
+
// --- Twitter Card ---
|
|
66
|
+
setMeta("twitter:card", image ? "summary_large_image" : "summary");
|
|
67
|
+
if (title)
|
|
68
|
+
setMeta("twitter:title", title);
|
|
69
|
+
if (description)
|
|
70
|
+
setMeta("twitter:description", description);
|
|
71
|
+
if (image)
|
|
72
|
+
setMeta("twitter:image", image);
|
|
73
|
+
if (twitterHandle)
|
|
74
|
+
setMeta("twitter:site", twitterHandle);
|
|
75
|
+
// --- Canonical ---
|
|
76
|
+
if (canonicalURL)
|
|
77
|
+
setLink("canonical", canonicalURL);
|
|
78
|
+
}, [title, description, image, url, siteName, type, twitterHandle, noIndex, canonical, keywords]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function buildSchema(type, data) {
|
|
82
|
+
switch (type) {
|
|
83
|
+
case "article": {
|
|
84
|
+
const d = data;
|
|
85
|
+
return {
|
|
86
|
+
"@context": "https://schema.org",
|
|
87
|
+
"@type": "Article",
|
|
88
|
+
headline: d.title,
|
|
89
|
+
author: d.author ? { "@type": "Person", name: d.author } : undefined,
|
|
90
|
+
datePublished: d.datePublished,
|
|
91
|
+
dateModified: d.dateModified || d.datePublished,
|
|
92
|
+
description: d.description,
|
|
93
|
+
image: d.image,
|
|
94
|
+
url: d.url,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
case "product": {
|
|
98
|
+
const d = data;
|
|
99
|
+
return {
|
|
100
|
+
"@context": "https://schema.org",
|
|
101
|
+
"@type": "Product",
|
|
102
|
+
name: d.name,
|
|
103
|
+
description: d.description,
|
|
104
|
+
image: d.image,
|
|
105
|
+
sku: d.sku,
|
|
106
|
+
brand: d.brand ? { "@type": "Brand", name: d.brand } : undefined,
|
|
107
|
+
offers: {
|
|
108
|
+
"@type": "Offer",
|
|
109
|
+
price: d.price,
|
|
110
|
+
priceCurrency: d.currency || "USD",
|
|
111
|
+
availability: d.availability
|
|
112
|
+
? `https://schema.org/${d.availability}`
|
|
113
|
+
: "https://schema.org/InStock",
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
case "organization": {
|
|
118
|
+
const d = data;
|
|
119
|
+
return {
|
|
120
|
+
"@context": "https://schema.org",
|
|
121
|
+
"@type": "Organization",
|
|
122
|
+
name: d.name,
|
|
123
|
+
url: d.url,
|
|
124
|
+
logo: d.logo,
|
|
125
|
+
description: d.description,
|
|
126
|
+
email: d.email,
|
|
127
|
+
telephone: d.phone,
|
|
128
|
+
address: d.address,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
case "breadcrumb": {
|
|
132
|
+
const d = data;
|
|
133
|
+
return {
|
|
134
|
+
"@context": "https://schema.org",
|
|
135
|
+
"@type": "BreadcrumbList",
|
|
136
|
+
itemListElement: d.items.map((item, i) => ({
|
|
137
|
+
"@type": "ListItem",
|
|
138
|
+
position: i + 1,
|
|
139
|
+
name: item.name,
|
|
140
|
+
item: item.url,
|
|
141
|
+
})),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
case "faq": {
|
|
145
|
+
const d = data;
|
|
146
|
+
return {
|
|
147
|
+
"@context": "https://schema.org",
|
|
148
|
+
"@type": "FAQPage",
|
|
149
|
+
mainEntity: d.questions.map((q) => ({
|
|
150
|
+
"@type": "Question",
|
|
151
|
+
name: q.question,
|
|
152
|
+
acceptedAnswer: { "@type": "Answer", text: q.answer },
|
|
153
|
+
})),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
case "person": {
|
|
157
|
+
const d = data;
|
|
158
|
+
return {
|
|
159
|
+
"@context": "https://schema.org",
|
|
160
|
+
"@type": "Person",
|
|
161
|
+
name: d.name,
|
|
162
|
+
url: d.url,
|
|
163
|
+
image: d.image,
|
|
164
|
+
jobTitle: d.jobTitle,
|
|
165
|
+
description: d.description,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
case "website": {
|
|
169
|
+
const d = data;
|
|
170
|
+
return {
|
|
171
|
+
"@context": "https://schema.org",
|
|
172
|
+
"@type": "WebSite",
|
|
173
|
+
name: d.name,
|
|
174
|
+
url: d.url,
|
|
175
|
+
description: d.description,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
default:
|
|
179
|
+
return { "@context": "https://schema.org" };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Schema - Generates structured JSON-LD data for SEO
|
|
184
|
+
* Supports: article, product, organization, breadcrumb, faq, person, website
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* <Schema type="article" data={{
|
|
188
|
+
* title: "Blog Title",
|
|
189
|
+
* author: "Virendra"
|
|
190
|
+
* }} />
|
|
191
|
+
*/
|
|
192
|
+
function Schema({ type, data }) {
|
|
193
|
+
const schema = buildSchema(type, data);
|
|
194
|
+
return (React.createElement("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(schema, null, 2) } }));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/******************************************************************************
|
|
198
|
+
Copyright (c) Microsoft Corporation.
|
|
199
|
+
|
|
200
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
201
|
+
purpose with or without fee is hereby granted.
|
|
202
|
+
|
|
203
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
204
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
205
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
206
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
207
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
208
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
209
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
210
|
+
***************************************************************************** */
|
|
211
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
function __rest(s, e) {
|
|
215
|
+
var t = {};
|
|
216
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
217
|
+
t[p] = s[p];
|
|
218
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
219
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
220
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
221
|
+
t[p[i]] = s[p[i]];
|
|
222
|
+
}
|
|
223
|
+
return t;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
227
|
+
var e = new Error(message);
|
|
228
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
function generateAlt(src, title) {
|
|
232
|
+
var _a;
|
|
233
|
+
if (title)
|
|
234
|
+
return title;
|
|
235
|
+
// Extract filename from src, clean it up
|
|
236
|
+
const filename = ((_a = src.split("/").pop()) === null || _a === void 0 ? void 0 : _a.split("?")[0]) || "";
|
|
237
|
+
const withoutExt = filename.replace(/\.[^.]+$/, "");
|
|
238
|
+
const humanized = withoutExt
|
|
239
|
+
.replace(/[-_]/g, " ")
|
|
240
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
241
|
+
.toLowerCase()
|
|
242
|
+
.trim();
|
|
243
|
+
return humanized || "image";
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* SEOImg - Smart image component for SEO
|
|
247
|
+
* ✅ Auto-generates alt text from title or filename
|
|
248
|
+
* ✅ Adds loading="lazy" by default
|
|
249
|
+
* ✅ Warns in development if src is missing
|
|
250
|
+
* ✅ Adds width/height to prevent CLS
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* <SEOImg src="/product.jpg" title="Running Shoes" />
|
|
254
|
+
*/
|
|
255
|
+
function SEOImg(_a) {
|
|
256
|
+
var { src, title, alt, loading = "lazy", width, height, className, style } = _a, rest = __rest(_a, ["src", "title", "alt", "loading", "width", "height", "className", "style"]);
|
|
257
|
+
const resolvedAlt = alt || generateAlt(src, title);
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
if (process.env.NODE_ENV === "development") {
|
|
260
|
+
if (!src) {
|
|
261
|
+
console.warn("[smart-seo-lite] <SEOImg> is missing the `src` prop.");
|
|
262
|
+
}
|
|
263
|
+
if (!alt && !title) {
|
|
264
|
+
console.warn(`[smart-seo-lite] <SEOImg src="${src}"> has no explicit 'alt' or 'title'. Auto-generated: "${resolvedAlt}". Consider providing explicit alt text for better SEO.`);
|
|
265
|
+
}
|
|
266
|
+
if (!width || !height) {
|
|
267
|
+
console.warn(`[smart-seo-lite] <SEOImg src="${src}"> is missing width/height props. This may cause Cumulative Layout Shift (CLS).`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}, [src, alt, title, resolvedAlt, width, height]);
|
|
271
|
+
if (!src)
|
|
272
|
+
return null;
|
|
273
|
+
return (React.createElement("img", Object.assign({ src: src, alt: resolvedAlt, loading: loading === "auto" ? undefined : loading, width: width, height: height, className: className, style: style }, rest)));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export { SEOImg, Schema, useSEO };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
|
|
5
|
+
function setMeta(name, content, attr = "name") {
|
|
6
|
+
if (typeof document === "undefined")
|
|
7
|
+
return;
|
|
8
|
+
let el = document.querySelector(`meta[${attr}="${name}"]`);
|
|
9
|
+
if (!el) {
|
|
10
|
+
el = document.createElement("meta");
|
|
11
|
+
el.setAttribute(attr, name);
|
|
12
|
+
document.head.appendChild(el);
|
|
13
|
+
}
|
|
14
|
+
el.setAttribute("content", content);
|
|
15
|
+
}
|
|
16
|
+
function setLink(rel, href) {
|
|
17
|
+
if (typeof document === "undefined")
|
|
18
|
+
return;
|
|
19
|
+
let el = document.querySelector(`link[rel="${rel}"]`);
|
|
20
|
+
if (!el) {
|
|
21
|
+
el = document.createElement("link");
|
|
22
|
+
el.setAttribute("rel", rel);
|
|
23
|
+
document.head.appendChild(el);
|
|
24
|
+
}
|
|
25
|
+
el.setAttribute("href", href);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* useSEO - Automatically handles:
|
|
29
|
+
* ✅ title, description
|
|
30
|
+
* ✅ Open Graph tags
|
|
31
|
+
* ✅ Twitter Card tags
|
|
32
|
+
* ✅ Canonical URL
|
|
33
|
+
* ✅ Keywords, robots
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* useSEO({
|
|
37
|
+
* title: "Home Page",
|
|
38
|
+
* description: "Best products online",
|
|
39
|
+
* image: "/banner.png"
|
|
40
|
+
* });
|
|
41
|
+
*/
|
|
42
|
+
function useSEO({ title, description, image, url, siteName = "My App", type = "website", twitterHandle, noIndex = false, canonical, keywords = [], }) {
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
if (typeof document === "undefined")
|
|
45
|
+
return;
|
|
46
|
+
const resolvedURL = url || (typeof window !== "undefined" ? window.location.href : "");
|
|
47
|
+
const canonicalURL = canonical || resolvedURL;
|
|
48
|
+
// --- Basic Meta ---
|
|
49
|
+
if (title)
|
|
50
|
+
document.title = title;
|
|
51
|
+
if (description)
|
|
52
|
+
setMeta("description", description);
|
|
53
|
+
if (keywords.length > 0)
|
|
54
|
+
setMeta("keywords", keywords.join(", "));
|
|
55
|
+
setMeta("robots", noIndex ? "noindex,nofollow" : "index,follow");
|
|
56
|
+
// --- Open Graph ---
|
|
57
|
+
if (title)
|
|
58
|
+
setMeta("og:title", title, "property");
|
|
59
|
+
if (description)
|
|
60
|
+
setMeta("og:description", description, "property");
|
|
61
|
+
if (image)
|
|
62
|
+
setMeta("og:image", image, "property");
|
|
63
|
+
if (resolvedURL)
|
|
64
|
+
setMeta("og:url", resolvedURL, "property");
|
|
65
|
+
setMeta("og:type", type, "property");
|
|
66
|
+
setMeta("og:site_name", siteName, "property");
|
|
67
|
+
// --- Twitter Card ---
|
|
68
|
+
setMeta("twitter:card", image ? "summary_large_image" : "summary");
|
|
69
|
+
if (title)
|
|
70
|
+
setMeta("twitter:title", title);
|
|
71
|
+
if (description)
|
|
72
|
+
setMeta("twitter:description", description);
|
|
73
|
+
if (image)
|
|
74
|
+
setMeta("twitter:image", image);
|
|
75
|
+
if (twitterHandle)
|
|
76
|
+
setMeta("twitter:site", twitterHandle);
|
|
77
|
+
// --- Canonical ---
|
|
78
|
+
if (canonicalURL)
|
|
79
|
+
setLink("canonical", canonicalURL);
|
|
80
|
+
}, [title, description, image, url, siteName, type, twitterHandle, noIndex, canonical, keywords]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function buildSchema(type, data) {
|
|
84
|
+
switch (type) {
|
|
85
|
+
case "article": {
|
|
86
|
+
const d = data;
|
|
87
|
+
return {
|
|
88
|
+
"@context": "https://schema.org",
|
|
89
|
+
"@type": "Article",
|
|
90
|
+
headline: d.title,
|
|
91
|
+
author: d.author ? { "@type": "Person", name: d.author } : undefined,
|
|
92
|
+
datePublished: d.datePublished,
|
|
93
|
+
dateModified: d.dateModified || d.datePublished,
|
|
94
|
+
description: d.description,
|
|
95
|
+
image: d.image,
|
|
96
|
+
url: d.url,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
case "product": {
|
|
100
|
+
const d = data;
|
|
101
|
+
return {
|
|
102
|
+
"@context": "https://schema.org",
|
|
103
|
+
"@type": "Product",
|
|
104
|
+
name: d.name,
|
|
105
|
+
description: d.description,
|
|
106
|
+
image: d.image,
|
|
107
|
+
sku: d.sku,
|
|
108
|
+
brand: d.brand ? { "@type": "Brand", name: d.brand } : undefined,
|
|
109
|
+
offers: {
|
|
110
|
+
"@type": "Offer",
|
|
111
|
+
price: d.price,
|
|
112
|
+
priceCurrency: d.currency || "USD",
|
|
113
|
+
availability: d.availability
|
|
114
|
+
? `https://schema.org/${d.availability}`
|
|
115
|
+
: "https://schema.org/InStock",
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
case "organization": {
|
|
120
|
+
const d = data;
|
|
121
|
+
return {
|
|
122
|
+
"@context": "https://schema.org",
|
|
123
|
+
"@type": "Organization",
|
|
124
|
+
name: d.name,
|
|
125
|
+
url: d.url,
|
|
126
|
+
logo: d.logo,
|
|
127
|
+
description: d.description,
|
|
128
|
+
email: d.email,
|
|
129
|
+
telephone: d.phone,
|
|
130
|
+
address: d.address,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
case "breadcrumb": {
|
|
134
|
+
const d = data;
|
|
135
|
+
return {
|
|
136
|
+
"@context": "https://schema.org",
|
|
137
|
+
"@type": "BreadcrumbList",
|
|
138
|
+
itemListElement: d.items.map((item, i) => ({
|
|
139
|
+
"@type": "ListItem",
|
|
140
|
+
position: i + 1,
|
|
141
|
+
name: item.name,
|
|
142
|
+
item: item.url,
|
|
143
|
+
})),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
case "faq": {
|
|
147
|
+
const d = data;
|
|
148
|
+
return {
|
|
149
|
+
"@context": "https://schema.org",
|
|
150
|
+
"@type": "FAQPage",
|
|
151
|
+
mainEntity: d.questions.map((q) => ({
|
|
152
|
+
"@type": "Question",
|
|
153
|
+
name: q.question,
|
|
154
|
+
acceptedAnswer: { "@type": "Answer", text: q.answer },
|
|
155
|
+
})),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
case "person": {
|
|
159
|
+
const d = data;
|
|
160
|
+
return {
|
|
161
|
+
"@context": "https://schema.org",
|
|
162
|
+
"@type": "Person",
|
|
163
|
+
name: d.name,
|
|
164
|
+
url: d.url,
|
|
165
|
+
image: d.image,
|
|
166
|
+
jobTitle: d.jobTitle,
|
|
167
|
+
description: d.description,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
case "website": {
|
|
171
|
+
const d = data;
|
|
172
|
+
return {
|
|
173
|
+
"@context": "https://schema.org",
|
|
174
|
+
"@type": "WebSite",
|
|
175
|
+
name: d.name,
|
|
176
|
+
url: d.url,
|
|
177
|
+
description: d.description,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
default:
|
|
181
|
+
return { "@context": "https://schema.org" };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Schema - Generates structured JSON-LD data for SEO
|
|
186
|
+
* Supports: article, product, organization, breadcrumb, faq, person, website
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* <Schema type="article" data={{
|
|
190
|
+
* title: "Blog Title",
|
|
191
|
+
* author: "Virendra"
|
|
192
|
+
* }} />
|
|
193
|
+
*/
|
|
194
|
+
function Schema({ type, data }) {
|
|
195
|
+
const schema = buildSchema(type, data);
|
|
196
|
+
return (React.createElement("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(schema, null, 2) } }));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/******************************************************************************
|
|
200
|
+
Copyright (c) Microsoft Corporation.
|
|
201
|
+
|
|
202
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
203
|
+
purpose with or without fee is hereby granted.
|
|
204
|
+
|
|
205
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
206
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
207
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
208
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
209
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
210
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
211
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
212
|
+
***************************************************************************** */
|
|
213
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
function __rest(s, e) {
|
|
217
|
+
var t = {};
|
|
218
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
219
|
+
t[p] = s[p];
|
|
220
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
221
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
222
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
223
|
+
t[p[i]] = s[p[i]];
|
|
224
|
+
}
|
|
225
|
+
return t;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
229
|
+
var e = new Error(message);
|
|
230
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
function generateAlt(src, title) {
|
|
234
|
+
var _a;
|
|
235
|
+
if (title)
|
|
236
|
+
return title;
|
|
237
|
+
// Extract filename from src, clean it up
|
|
238
|
+
const filename = ((_a = src.split("/").pop()) === null || _a === void 0 ? void 0 : _a.split("?")[0]) || "";
|
|
239
|
+
const withoutExt = filename.replace(/\.[^.]+$/, "");
|
|
240
|
+
const humanized = withoutExt
|
|
241
|
+
.replace(/[-_]/g, " ")
|
|
242
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
243
|
+
.toLowerCase()
|
|
244
|
+
.trim();
|
|
245
|
+
return humanized || "image";
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* SEOImg - Smart image component for SEO
|
|
249
|
+
* ✅ Auto-generates alt text from title or filename
|
|
250
|
+
* ✅ Adds loading="lazy" by default
|
|
251
|
+
* ✅ Warns in development if src is missing
|
|
252
|
+
* ✅ Adds width/height to prevent CLS
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* <SEOImg src="/product.jpg" title="Running Shoes" />
|
|
256
|
+
*/
|
|
257
|
+
function SEOImg(_a) {
|
|
258
|
+
var { src, title, alt, loading = "lazy", width, height, className, style } = _a, rest = __rest(_a, ["src", "title", "alt", "loading", "width", "height", "className", "style"]);
|
|
259
|
+
const resolvedAlt = alt || generateAlt(src, title);
|
|
260
|
+
React.useEffect(() => {
|
|
261
|
+
if (process.env.NODE_ENV === "development") {
|
|
262
|
+
if (!src) {
|
|
263
|
+
console.warn("[smart-seo-lite] <SEOImg> is missing the `src` prop.");
|
|
264
|
+
}
|
|
265
|
+
if (!alt && !title) {
|
|
266
|
+
console.warn(`[smart-seo-lite] <SEOImg src="${src}"> has no explicit 'alt' or 'title'. Auto-generated: "${resolvedAlt}". Consider providing explicit alt text for better SEO.`);
|
|
267
|
+
}
|
|
268
|
+
if (!width || !height) {
|
|
269
|
+
console.warn(`[smart-seo-lite] <SEOImg src="${src}"> is missing width/height props. This may cause Cumulative Layout Shift (CLS).`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}, [src, alt, title, resolvedAlt, width, height]);
|
|
273
|
+
if (!src)
|
|
274
|
+
return null;
|
|
275
|
+
return (React.createElement("img", Object.assign({ src: src, alt: resolvedAlt, loading: loading === "auto" ? undefined : loading, width: width, height: height, className: className, style: style }, rest)));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
exports.SEOImg = SEOImg;
|
|
279
|
+
exports.Schema = Schema;
|
|
280
|
+
exports.useSEO = useSEO;
|
package/dist/useSEO.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface SEOProps {
|
|
2
|
+
title?: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
image?: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
siteName?: string;
|
|
7
|
+
type?: "website" | "article" | "product";
|
|
8
|
+
twitterHandle?: string;
|
|
9
|
+
noIndex?: boolean;
|
|
10
|
+
canonical?: string;
|
|
11
|
+
keywords?: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* useSEO - Automatically handles:
|
|
15
|
+
* ✅ title, description
|
|
16
|
+
* ✅ Open Graph tags
|
|
17
|
+
* ✅ Twitter Card tags
|
|
18
|
+
* ✅ Canonical URL
|
|
19
|
+
* ✅ Keywords, robots
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* useSEO({
|
|
23
|
+
* title: "Home Page",
|
|
24
|
+
* description: "Best products online",
|
|
25
|
+
* image: "/banner.png"
|
|
26
|
+
* });
|
|
27
|
+
*/
|
|
28
|
+
export declare function useSEO({ title, description, image, url, siteName, type, twitterHandle, noIndex, canonical, keywords, }: SEOProps): void;
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "smart-seo-lite",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Add SEO to your React/Next.js app in 3 lines of code. Auto meta tags, JSON-LD schema, and smart image SEO.",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.esm.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "rollup -c",
|
|
14
|
+
"dev": "rollup -c -w",
|
|
15
|
+
"lint": "eslint src --ext .ts,.tsx"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"seo",
|
|
19
|
+
"react",
|
|
20
|
+
"nextjs",
|
|
21
|
+
"meta-tags",
|
|
22
|
+
"open-graph",
|
|
23
|
+
"schema",
|
|
24
|
+
"json-ld",
|
|
25
|
+
"twitter-card",
|
|
26
|
+
"canonical",
|
|
27
|
+
"image-seo"
|
|
28
|
+
],
|
|
29
|
+
"author": "Virendra",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": ">=17.0.0",
|
|
33
|
+
"react-dom": ">=17.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@rollup/plugin-typescript": "^11.0.0",
|
|
37
|
+
"@types/node": "^25.6.0",
|
|
38
|
+
"@types/react": "^19.2.14",
|
|
39
|
+
"rollup": "^3.0.0",
|
|
40
|
+
"tslib": "^2.8.1",
|
|
41
|
+
"typescript": "^5.0.0"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/virendra2902/smart-seo-lite"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://virendra2902.github.io/smart-seo-lite",
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/virendra2902/smart-seo-lite/issues"
|
|
50
|
+
}
|
|
51
|
+
}
|