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 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
+ [![npm version](https://img.shields.io/npm/v/smart-seo-lite)](https://npmjs.com/package/smart-seo-lite)
6
+ [![license](https://img.shields.io/npm/l/smart-seo-lite)](LICENSE)
7
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/smart-seo-lite)](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
@@ -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 {};
@@ -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 {};
@@ -0,0 +1,4 @@
1
+ export { useSEO } from "./useSEO";
2
+ export type { SEOProps } from "./useSEO";
3
+ export { Schema } from "./Schema";
4
+ export { SEOImg } from "./SEOImg";
@@ -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;
@@ -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
+ }