react-client-seo 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/README.md ADDED
@@ -0,0 +1,288 @@
1
+ # react-client-seo
2
+
3
+ A lightweight, production-ready client-side SEO renderer for React and Vite applications. No server-side rendering required!
4
+
5
+ ## Features
6
+
7
+ - ✅ **Lightweight** - No heavy dependencies
8
+ - ✅ **Client-side only** - Works with React and Vite apps
9
+ - ✅ **TypeScript support** - Full type definitions included
10
+ - ✅ **React 17+ & 18+** - Compatible with both versions
11
+ - ✅ **Component & Hook APIs** - Use `<Seo />` component or `useSeo()` hook
12
+ - ✅ **Auto-updates** - Automatically creates/updates tags, avoids duplicates
13
+ - ✅ **Comprehensive** - Supports title, meta tags, Open Graph, Twitter Cards, JSON-LD, and custom meta tags
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install react-client-seo
19
+ ```
20
+
21
+ ```bash
22
+ yarn add react-client-seo
23
+ ```
24
+
25
+ ```bash
26
+ pnpm add react-client-seo
27
+ ```
28
+
29
+ ## Peer Dependencies
30
+
31
+ - `react` >= 17.0.0
32
+ - `react-dom` >= 17.0.0
33
+
34
+ ## Usage
35
+
36
+ ### Component API
37
+
38
+ ```tsx
39
+ import { Seo } from 'react-client-seo';
40
+
41
+ function App() {
42
+ return (
43
+ <>
44
+ <Seo
45
+ title="My Page Title"
46
+ description="This is a great page about React SEO"
47
+ keywords={['react', 'seo', 'meta tags']}
48
+ canonical="https://example.com/page"
49
+ />
50
+ <div>Your content here</div>
51
+ </>
52
+ );
53
+ }
54
+ ```
55
+
56
+ ### Hook API
57
+
58
+ ```tsx
59
+ import { useSeo } from 'react-client-seo';
60
+ import { useEffect } from 'react';
61
+
62
+ function MyComponent() {
63
+ const { updateSeo } = useSeo();
64
+
65
+ useEffect(() => {
66
+ updateSeo({
67
+ title: 'My Page Title',
68
+ description: 'Page description',
69
+ keywords: 'react, seo',
70
+ });
71
+ }, []);
72
+
73
+ return <div>Content</div>;
74
+ }
75
+ ```
76
+
77
+ ## Examples
78
+
79
+ ### Basic Usage
80
+
81
+ ```tsx
82
+ <Seo
83
+ title="Home Page"
84
+ description="Welcome to our website"
85
+ keywords="home, welcome, website"
86
+ />
87
+ ```
88
+
89
+ ### Open Graph Tags
90
+
91
+ ```tsx
92
+ <Seo
93
+ title="Article Title"
94
+ description="Article description"
95
+ ogImage="https://example.com/image.jpg"
96
+ ogType="article"
97
+ ogUrl="https://example.com/article"
98
+ ogSiteName="My Website"
99
+ ogLocale="en_US"
100
+ />
101
+ ```
102
+
103
+ ### Twitter Cards
104
+
105
+ ```tsx
106
+ <Seo
107
+ title="Article Title"
108
+ description="Article description"
109
+ twitterCard="summary_large_image"
110
+ twitterSite="@username"
111
+ twitterCreator="@author"
112
+ twitterImage="https://example.com/image.jpg"
113
+ twitterImageAlt="Article image"
114
+ />
115
+ ```
116
+
117
+ ### JSON-LD Structured Data
118
+
119
+ ```tsx
120
+ <Seo
121
+ title="Product Page"
122
+ description="Amazing product"
123
+ jsonLd={{
124
+ "@context": "https://schema.org",
125
+ "@type": "Product",
126
+ "name": "Amazing Product",
127
+ "description": "This is an amazing product",
128
+ "image": "https://example.com/product.jpg",
129
+ "offers": {
130
+ "@type": "Offer",
131
+ "price": "99.99",
132
+ "priceCurrency": "USD"
133
+ }
134
+ }}
135
+ />
136
+ ```
137
+
138
+ ### Advanced Open Graph with Custom Properties
139
+
140
+ ```tsx
141
+ <Seo
142
+ title="Article"
143
+ openGraph={{
144
+ title: "Custom OG Title",
145
+ description: "Custom OG description",
146
+ type: "article",
147
+ url: "https://example.com/article",
148
+ image: "https://example.com/image.jpg",
149
+ "og:article:author": "John Doe",
150
+ "og:article:published_time": "2024-01-01T00:00:00Z",
151
+ }}
152
+ />
153
+ ```
154
+
155
+ ### Custom Meta Tags
156
+
157
+ ```tsx
158
+ <Seo
159
+ title="Page Title"
160
+ customMeta={[
161
+ { name: "author", content: "John Doe" },
162
+ { name: "robots", content: "index, follow" },
163
+ { property: "custom:property", content: "value" },
164
+ { httpEquiv: "X-UA-Compatible", content: "IE=edge" },
165
+ ]}
166
+ />
167
+ ```
168
+
169
+ ### Complete Example
170
+
171
+ ```tsx
172
+ import { Seo } from 'react-client-seo';
173
+
174
+ function ProductPage({ product }) {
175
+ return (
176
+ <>
177
+ <Seo
178
+ title={`${product.name} - My Store`}
179
+ description={product.description}
180
+ keywords={product.tags}
181
+ canonical={`https://mystore.com/products/${product.id}`}
182
+ ogImage={product.image}
183
+ ogType="product"
184
+ ogUrl={`https://mystore.com/products/${product.id}`}
185
+ ogSiteName="My Store"
186
+ twitterCard="summary_large_image"
187
+ twitterSite="@mystore"
188
+ twitterImage={product.image}
189
+ jsonLd={{
190
+ "@context": "https://schema.org",
191
+ "@type": "Product",
192
+ "name": product.name,
193
+ "description": product.description,
194
+ "image": product.image,
195
+ "offers": {
196
+ "@type": "Offer",
197
+ "price": product.price,
198
+ "priceCurrency": "USD",
199
+ "availability": "https://schema.org/InStock"
200
+ }
201
+ }}
202
+ customMeta={[
203
+ { name: "robots", content: "index, follow" },
204
+ ]}
205
+ />
206
+ <div>
207
+ <h1>{product.name}</h1>
208
+ <p>{product.description}</p>
209
+ </div>
210
+ </>
211
+ );
212
+ }
213
+ ```
214
+
215
+ ## API Reference
216
+
217
+ ### `<Seo />` Component Props
218
+
219
+ | Prop | Type | Description |
220
+ |------|------|-------------|
221
+ | `title` | `string` | Page title |
222
+ | `description` | `string` | Meta description |
223
+ | `keywords` | `string \| string[]` | Meta keywords (comma-separated string or array) |
224
+ | `canonical` | `string` | Canonical URL |
225
+ | `ogImage` | `string` | Open Graph image URL |
226
+ | `ogType` | `string` | Open Graph type (e.g., "article", "website") |
227
+ | `ogUrl` | `string` | Open Graph URL |
228
+ | `ogTitle` | `string` | Open Graph title (defaults to `title`) |
229
+ | `ogDescription` | `string` | Open Graph description (defaults to `description`) |
230
+ | `ogSiteName` | `string` | Open Graph site name |
231
+ | `ogLocale` | `string` | Open Graph locale |
232
+ | `twitterCard` | `'summary' \| 'summary_large_image' \| 'app' \| 'player'` | Twitter Card type |
233
+ | `twitterSite` | `string` | Twitter site handle |
234
+ | `twitterCreator` | `string` | Twitter creator handle |
235
+ | `twitterTitle` | `string` | Twitter title (defaults to `title`) |
236
+ | `twitterDescription` | `string` | Twitter description (defaults to `description`) |
237
+ | `twitterImage` | `string` | Twitter image URL |
238
+ | `twitterImageAlt` | `string` | Twitter image alt text |
239
+ | `jsonLd` | `object \| object[]` | JSON-LD structured data |
240
+ | `customMeta` | `CustomMeta[]` | Custom meta tags |
241
+ | `openGraph` | `OpenGraphMeta` | Additional Open Graph properties |
242
+ | `twitter` | `TwitterCardMeta` | Additional Twitter Card properties |
243
+
244
+ ### `useSeo()` Hook
245
+
246
+ Returns an object with:
247
+
248
+ - `updateSeo(props: SeoProps)` - Update SEO tags
249
+ - `clearSeo()` - Clear managed SEO tags (canonical, JSON-LD)
250
+
251
+ ## TypeScript
252
+
253
+ Full TypeScript support is included. Import types as needed:
254
+
255
+ ```tsx
256
+ import type { SeoProps, OpenGraphMeta, TwitterCardMeta, CustomMeta, JsonLd } from 'react-client-seo';
257
+ ```
258
+
259
+ ## How It Works
260
+
261
+ - The component/hook automatically creates meta tags if they don't exist
262
+ - Updates existing tags if they're already present
263
+ - Avoids duplicate tags
264
+ - Cleans up JSON-LD scripts on unmount (component API)
265
+ - Works entirely client-side - no SSR required
266
+
267
+ ## Publishing to NPM
268
+
269
+ 1. Update version in `package.json`
270
+ 2. Build the package:
271
+ ```bash
272
+ npm run build
273
+ ```
274
+ 3. Publish:
275
+ ```bash
276
+ npm publish
277
+ ```
278
+
279
+ The `prepare` script automatically runs before publishing, ensuring the package is built.
280
+
281
+ ## License
282
+
283
+ MIT
284
+
285
+ ## Contributing
286
+
287
+ Contributions are welcome! Please feel free to submit a Pull Request.
288
+
package/dist/Seo.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { SeoProps } from './types';
2
+
3
+ /**
4
+ * SEO Component
5
+ *
6
+ * Renders and updates SEO meta tags in the document head.
7
+ * Returns null (no UI rendering).
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * <Seo
12
+ * title="My Page Title"
13
+ * description="Page description"
14
+ * keywords={['react', 'seo']}
15
+ * canonical="https://example.com/page"
16
+ * ogImage="https://example.com/image.jpg"
17
+ * jsonLd={{ "@context": "https://schema.org", "@type": "WebPage" }}
18
+ * />
19
+ * ```
20
+ */
21
+ export declare function Seo({ title, description, keywords, canonical, ogImage, ogType, ogUrl, ogTitle, ogDescription, ogSiteName, ogLocale, twitterCard, twitterSite, twitterCreator, twitterTitle, twitterDescription, twitterImage, twitterImageAlt, jsonLd, customMeta, openGraph, twitter, }: SeoProps): null;
22
+ //# sourceMappingURL=Seo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Seo.d.ts","sourceRoot":"","sources":["../src/Seo.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAcxC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,GAAG,CAAC,EAClB,KAAK,EACL,WAAW,EACX,QAAQ,EACR,SAAS,EACT,OAAO,EACP,MAAM,EACN,KAAK,EACL,OAAO,EACP,aAAa,EACb,UAAU,EACV,QAAQ,EACR,WAAW,EACX,WAAW,EACX,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,MAAM,EACN,UAAU,EACV,SAAS,EACT,OAAO,GACR,EAAE,QAAQ,QAgIV"}
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const W=require("react");function T(e,t){const i=`meta[${e}="${t}"]`;let n=document.querySelector(i);return n||(n=document.createElement("meta"),n.setAttribute(e,t),document.head.appendChild(n)),n}function c(e,t){if(!t)return;T("name",e).setAttribute("content",t)}function f(e,t){if(t==null||t==="")return;T("property",e).setAttribute("content",String(t))}function $(e){e&&(document.title=e)}function B(e){if(!e)return;let t=document.querySelector('link[rel="canonical"]');t||(t=document.createElement("link"),t.setAttribute("rel","canonical"),document.head.appendChild(t)),t.setAttribute("href",e)}function G(){const e=document.querySelector('link[rel="canonical"]');e&&e.remove()}function H(e){e&&(e.title&&f("og:title",e.title),e.description&&f("og:description",e.description),e.type&&f("og:type",e.type),e.url&&f("og:url",e.url),e.image&&f("og:image",e.image),e.imageWidth&&f("og:image:width",e.imageWidth),e.imageHeight&&f("og:image:height",e.imageHeight),e.imageAlt&&f("og:image:alt",e.imageAlt),e.siteName&&f("og:site_name",e.siteName),e.locale&&f("og:locale",e.locale),Object.keys(e).forEach(t=>{if(t.startsWith("og:")&&t!=="og:title"&&t!=="og:description"&&t!=="og:type"&&t!=="og:url"&&t!=="og:image"&&t!=="og:imageWidth"&&t!=="og:imageHeight"&&t!=="og:imageAlt"&&t!=="og:siteName"&&t!=="og:locale"){const i=e[t];i!=null&&i!==""&&f(t,i)}}))}function L(e){e&&(e.card&&c("twitter:card",e.card),e.site&&c("twitter:site",e.site),e.creator&&c("twitter:creator",e.creator),e.title&&c("twitter:title",e.title),e.description&&c("twitter:description",e.description),e.image&&c("twitter:image",e.image),e.imageAlt&&c("twitter:image:alt",e.imageAlt),Object.keys(e).forEach(t=>{if(t.startsWith("twitter:")&&t!=="twitter:card"&&t!=="twitter:site"&&t!=="twitter:creator"&&t!=="twitter:title"&&t!=="twitter:description"&&t!=="twitter:image"&&t!=="twitter:imageAlt"){const i=e[t];i!=null&&i!==""&&c(t,String(i))}}))}function x(e){!e||!Array.isArray(e)||e.forEach(t=>{if(!t.content)return;let i=null;if(t.name)i=T("name",t.name);else if(t.property)i=T("property",t.property);else if(t.httpEquiv){const n=`meta[http-equiv="${t.httpEquiv}"]`;i=document.querySelector(n),i||(i=document.createElement("meta"),i.setAttribute("http-equiv",t.httpEquiv),document.head.appendChild(i))}else if(t.charset){let n=document.querySelector("meta[charset]");n||(n=document.createElement("meta"),n.setAttribute("charset",t.charset),document.head.insertBefore(n,document.head.firstChild));return}i&&i.setAttribute("content",t.content)})}function M(e,t){const i=document.createElement("script");i.type="application/ld+json";{i.id=t;const n=document.getElementById(t);n&&n.remove()}return i.textContent=JSON.stringify(e),document.head.appendChild(i),()=>{i.remove()}}function K(e){const t=document.getElementById(e);t&&t.getAttribute("type")==="application/ld+json"&&t.remove()}function P(e){return e?Array.isArray(e)?e.join(", "):e:""}const z="react-client-seo-jsonld";function F({title:e,description:t,keywords:i,canonical:n,ogImage:r,ogType:C,ogUrl:u,ogTitle:d,ogDescription:m,ogSiteName:p,ogLocale:h,twitterCard:g,twitterSite:A,twitterCreator:b,twitterTitle:S,twitterDescription:v,twitterImage:E,twitterImageAlt:y,jsonLd:j,customMeta:O,openGraph:N,twitter:D}){return W.useEffect(()=>{e&&$(e),t&&c("description",t);const J=P(i);J&&c("keywords",J),n&&B(n);const o={...N};(d||e)&&(o.title=d||e),(m||t)&&(o.description=m||t),C&&(o.type=C),(u||n)&&(o.url=u||n),r&&(o.image=r),p&&(o.siteName=p),h&&(o.locale=h),Object.keys(o).length>0&&H(o);const a={...D};g&&(a.card=g),A&&(a.site=A),b&&(a.creator=b),(S||e)&&(a.title=S||e),(v||t)&&(a.description=v||t),(E||r)&&(a.image=E||r),y&&(a.imageAlt=y),Object.keys(a).length>0&&L(a),O&&x(O);let q=null;return j&&(q=M(j,z)),()=>{q&&q()}},[e,t,i,n,r,C,u,d,m,p,h,g,A,b,S,v,E,y,j,O,N,D]),null}const _="react-client-seo-jsonld";function Q(){const e=W.useCallback(i=>{const{title:n,description:r,keywords:C,canonical:u,ogImage:d,ogType:m,ogUrl:p,ogTitle:h,ogDescription:g,ogSiteName:A,ogLocale:b,twitterCard:S,twitterSite:v,twitterCreator:E,twitterTitle:y,twitterDescription:j,twitterImage:O,twitterImageAlt:N,jsonLd:D,customMeta:J,openGraph:o,twitter:a}=i;n&&$(n),r&&c("description",r);const q=P(C);q&&c("keywords",q),u&&B(u);const l={...o};(h||n)&&(l.title=h||n),(g||r)&&(l.description=g||r),m&&(l.type=m),(p||u)&&(l.url=p||u),d&&(l.image=d),A&&(l.siteName=A),b&&(l.locale=b),Object.keys(l).length>0&&H(l);const s={...a};S&&(s.card=S),v&&(s.site=v),E&&(s.creator=E),(y||n)&&(s.title=y||n),(j||r)&&(s.description=j||r),(O||d)&&(s.image=O||d),N&&(s.imageAlt=N),Object.keys(s).length>0&&L(s),J&&x(J),D&&M(D,_)},[]),t=W.useCallback(()=>{G(),K(_)},[]);return{updateSeo:e,clearSeo:t}}exports.Seo=F;exports.useSeo=Q;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/utils.ts","../src/Seo.tsx","../src/useSeo.ts"],"sourcesContent":["import type { CustomMeta, JsonLd, OpenGraphMeta, TwitterCardMeta } from './types';\r\n\r\n/**\r\n * Get or create a meta tag element\r\n */\r\nfunction getOrCreateMetaTag(attribute: 'name' | 'property', value: string): HTMLMetaElement {\r\n const selector = `meta[${attribute}=\"${value}\"]`;\r\n let element = document.querySelector<HTMLMetaElement>(selector);\r\n\r\n if (!element) {\r\n element = document.createElement('meta');\r\n element.setAttribute(attribute, value);\r\n document.head.appendChild(element);\r\n }\r\n\r\n return element;\r\n}\r\n\r\n/**\r\n * Set a meta tag by name\r\n */\r\nexport function setMetaTag(name: string, content: string): void {\r\n if (!content) return;\r\n\r\n const element = getOrCreateMetaTag('name', name);\r\n element.setAttribute('content', content);\r\n}\r\n\r\n/**\r\n * Set a meta tag by property (for Open Graph)\r\n */\r\nexport function setMetaProperty(property: string, content: string | number): void {\r\n if (content === undefined || content === null || content === '') return;\r\n\r\n const element = getOrCreateMetaTag('property', property);\r\n element.setAttribute('content', String(content));\r\n}\r\n\r\n/**\r\n * Set the page title\r\n */\r\nexport function setTitle(title: string): void {\r\n if (!title) return;\r\n document.title = title;\r\n}\r\n\r\n/**\r\n * Set canonical URL\r\n */\r\nexport function setCanonical(url: string): void {\r\n if (!url) return;\r\n\r\n let link = document.querySelector<HTMLLinkElement>('link[rel=\"canonical\"]');\r\n if (!link) {\r\n link = document.createElement('link');\r\n link.setAttribute('rel', 'canonical');\r\n document.head.appendChild(link);\r\n }\r\n link.setAttribute('href', url);\r\n}\r\n\r\n/**\r\n * Remove canonical URL\r\n */\r\nexport function removeCanonical(): void {\r\n const link = document.querySelector<HTMLLinkElement>('link[rel=\"canonical\"]');\r\n if (link) {\r\n link.remove();\r\n }\r\n}\r\n\r\n/**\r\n * Set Open Graph meta tags\r\n */\r\nexport function setOpenGraphTags(og: OpenGraphMeta): void {\r\n if (!og) return;\r\n\r\n // Standard Open Graph properties\r\n if (og.title) setMetaProperty('og:title', og.title);\r\n if (og.description) setMetaProperty('og:description', og.description);\r\n if (og.type) setMetaProperty('og:type', og.type);\r\n if (og.url) setMetaProperty('og:url', og.url);\r\n if (og.image) setMetaProperty('og:image', og.image);\r\n if (og.imageWidth) setMetaProperty('og:image:width', og.imageWidth);\r\n if (og.imageHeight) setMetaProperty('og:image:height', og.imageHeight);\r\n if (og.imageAlt) setMetaProperty('og:image:alt', og.imageAlt);\r\n if (og.siteName) setMetaProperty('og:site_name', og.siteName);\r\n if (og.locale) setMetaProperty('og:locale', og.locale);\r\n\r\n // Handle additional custom og:* properties\r\n Object.keys(og).forEach((key) => {\r\n if (key.startsWith('og:') && key !== 'og:title' && key !== 'og:description' && \r\n key !== 'og:type' && key !== 'og:url' && key !== 'og:image' && \r\n key !== 'og:imageWidth' && key !== 'og:imageHeight' && \r\n key !== 'og:imageAlt' && key !== 'og:siteName' && key !== 'og:locale') {\r\n const value = og[key as keyof OpenGraphMeta];\r\n if (value !== undefined && value !== null && value !== '') {\r\n setMetaProperty(key, value as string | number);\r\n }\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Set Twitter Card meta tags\r\n */\r\nexport function setTwitterCardTags(twitter: TwitterCardMeta): void {\r\n if (!twitter) return;\r\n\r\n if (twitter.card) setMetaTag('twitter:card', twitter.card);\r\n if (twitter.site) setMetaTag('twitter:site', twitter.site);\r\n if (twitter.creator) setMetaTag('twitter:creator', twitter.creator);\r\n if (twitter.title) setMetaTag('twitter:title', twitter.title);\r\n if (twitter.description) setMetaTag('twitter:description', twitter.description);\r\n if (twitter.image) setMetaTag('twitter:image', twitter.image);\r\n if (twitter.imageAlt) setMetaTag('twitter:image:alt', twitter.imageAlt);\r\n\r\n // Handle additional custom twitter:* properties\r\n Object.keys(twitter).forEach((key) => {\r\n if (key.startsWith('twitter:') && key !== 'twitter:card' && key !== 'twitter:site' && \r\n key !== 'twitter:creator' && key !== 'twitter:title' && \r\n key !== 'twitter:description' && key !== 'twitter:image' && \r\n key !== 'twitter:imageAlt') {\r\n const value = twitter[key as keyof TwitterCardMeta];\r\n if (value !== undefined && value !== null && value !== '') {\r\n setMetaTag(key, String(value));\r\n }\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Set custom meta tags\r\n */\r\nexport function setCustomMetaTags(customMeta: CustomMeta[]): void {\r\n if (!customMeta || !Array.isArray(customMeta)) return;\r\n\r\n customMeta.forEach((meta) => {\r\n if (!meta.content) return;\r\n\r\n let element: HTMLMetaElement | null = null;\r\n\r\n if (meta.name) {\r\n element = getOrCreateMetaTag('name', meta.name);\r\n } else if (meta.property) {\r\n element = getOrCreateMetaTag('property', meta.property);\r\n } else if (meta.httpEquiv) {\r\n const selector = `meta[http-equiv=\"${meta.httpEquiv}\"]`;\r\n element = document.querySelector<HTMLMetaElement>(selector);\r\n if (!element) {\r\n element = document.createElement('meta');\r\n element.setAttribute('http-equiv', meta.httpEquiv);\r\n document.head.appendChild(element);\r\n }\r\n } else if (meta.charset) {\r\n let charsetElement = document.querySelector<HTMLMetaElement>('meta[charset]');\r\n if (!charsetElement) {\r\n charsetElement = document.createElement('meta');\r\n charsetElement.setAttribute('charset', meta.charset);\r\n document.head.insertBefore(charsetElement, document.head.firstChild);\r\n }\r\n return;\r\n }\r\n\r\n if (element) {\r\n element.setAttribute('content', meta.content);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Inject JSON-LD structured data\r\n */\r\nexport function injectJsonLd(data: JsonLd, id?: string): () => void {\r\n const script = document.createElement('script');\r\n script.type = 'application/ld+json';\r\n if (id) {\r\n script.id = id;\r\n // Remove existing script with same id\r\n const existing = document.getElementById(id);\r\n if (existing) {\r\n existing.remove();\r\n }\r\n }\r\n script.textContent = JSON.stringify(data);\r\n document.head.appendChild(script);\r\n\r\n // Return cleanup function\r\n return () => {\r\n script.remove();\r\n };\r\n}\r\n\r\n/**\r\n * Remove JSON-LD script by id\r\n */\r\nexport function removeJsonLd(id: string): void {\r\n const script = document.getElementById(id);\r\n if (script && script.getAttribute('type') === 'application/ld+json') {\r\n script.remove();\r\n }\r\n}\r\n\r\n/**\r\n * Format keywords as string\r\n */\r\nexport function formatKeywords(keywords: string | string[] | undefined): string {\r\n if (!keywords) return '';\r\n if (Array.isArray(keywords)) {\r\n return keywords.join(', ');\r\n }\r\n return keywords;\r\n}\r\n\r\n","import { useEffect } from 'react';\r\nimport type { SeoProps } from './types';\r\nimport {\r\n setTitle,\r\n setMetaTag,\r\n setCanonical,\r\n setOpenGraphTags,\r\n setTwitterCardTags,\r\n setCustomMetaTags,\r\n injectJsonLd,\r\n formatKeywords,\r\n} from './utils';\r\n\r\nconst JSON_LD_ID = 'react-client-seo-jsonld';\r\n\r\n/**\r\n * SEO Component\r\n * \r\n * Renders and updates SEO meta tags in the document head.\r\n * Returns null (no UI rendering).\r\n * \r\n * @example\r\n * ```tsx\r\n * <Seo\r\n * title=\"My Page Title\"\r\n * description=\"Page description\"\r\n * keywords={['react', 'seo']}\r\n * canonical=\"https://example.com/page\"\r\n * ogImage=\"https://example.com/image.jpg\"\r\n * jsonLd={{ \"@context\": \"https://schema.org\", \"@type\": \"WebPage\" }}\r\n * />\r\n * ```\r\n */\r\nexport function Seo({\r\n title,\r\n description,\r\n keywords,\r\n canonical,\r\n ogImage,\r\n ogType,\r\n ogUrl,\r\n ogTitle,\r\n ogDescription,\r\n ogSiteName,\r\n ogLocale,\r\n twitterCard,\r\n twitterSite,\r\n twitterCreator,\r\n twitterTitle,\r\n twitterDescription,\r\n twitterImage,\r\n twitterImageAlt,\r\n jsonLd,\r\n customMeta,\r\n openGraph,\r\n twitter,\r\n}: SeoProps) {\r\n useEffect(() => {\r\n // Set title\r\n if (title) {\r\n setTitle(title);\r\n }\r\n\r\n // Set description\r\n if (description) {\r\n setMetaTag('description', description);\r\n }\r\n\r\n // Set keywords\r\n const keywordsStr = formatKeywords(keywords);\r\n if (keywordsStr) {\r\n setMetaTag('keywords', keywordsStr);\r\n }\r\n\r\n // Set canonical URL\r\n if (canonical) {\r\n setCanonical(canonical);\r\n }\r\n\r\n // Build Open Graph object\r\n const ogData: any = {\r\n ...openGraph,\r\n };\r\n\r\n if (ogTitle || title) {\r\n ogData.title = ogTitle || title;\r\n }\r\n if (ogDescription || description) {\r\n ogData.description = ogDescription || description;\r\n }\r\n if (ogType) {\r\n ogData.type = ogType;\r\n }\r\n if (ogUrl || canonical) {\r\n ogData.url = ogUrl || canonical;\r\n }\r\n if (ogImage) {\r\n ogData.image = ogImage;\r\n }\r\n if (ogSiteName) {\r\n ogData.siteName = ogSiteName;\r\n }\r\n if (ogLocale) {\r\n ogData.locale = ogLocale;\r\n }\r\n\r\n if (Object.keys(ogData).length > 0) {\r\n setOpenGraphTags(ogData);\r\n }\r\n\r\n // Build Twitter Card object\r\n const twitterData: any = {\r\n ...twitter,\r\n };\r\n\r\n if (twitterCard) {\r\n twitterData.card = twitterCard;\r\n }\r\n if (twitterSite) {\r\n twitterData.site = twitterSite;\r\n }\r\n if (twitterCreator) {\r\n twitterData.creator = twitterCreator;\r\n }\r\n if (twitterTitle || title) {\r\n twitterData.title = twitterTitle || title;\r\n }\r\n if (twitterDescription || description) {\r\n twitterData.description = twitterDescription || description;\r\n }\r\n if (twitterImage || ogImage) {\r\n twitterData.image = twitterImage || ogImage;\r\n }\r\n if (twitterImageAlt) {\r\n twitterData.imageAlt = twitterImageAlt;\r\n }\r\n\r\n if (Object.keys(twitterData).length > 0) {\r\n setTwitterCardTags(twitterData);\r\n }\r\n\r\n // Set custom meta tags\r\n if (customMeta) {\r\n setCustomMetaTags(customMeta);\r\n }\r\n\r\n // Inject JSON-LD\r\n let cleanupJsonLd: (() => void) | null = null;\r\n if (jsonLd) {\r\n cleanupJsonLd = injectJsonLd(jsonLd, JSON_LD_ID);\r\n }\r\n\r\n // Cleanup function\r\n return () => {\r\n if (cleanupJsonLd) {\r\n cleanupJsonLd();\r\n }\r\n };\r\n }, [\r\n title,\r\n description,\r\n keywords,\r\n canonical,\r\n ogImage,\r\n ogType,\r\n ogUrl,\r\n ogTitle,\r\n ogDescription,\r\n ogSiteName,\r\n ogLocale,\r\n twitterCard,\r\n twitterSite,\r\n twitterCreator,\r\n twitterTitle,\r\n twitterDescription,\r\n twitterImage,\r\n twitterImageAlt,\r\n jsonLd,\r\n customMeta,\r\n openGraph,\r\n twitter,\r\n ]);\r\n\r\n return null;\r\n}\r\n\r\n","import { useCallback } from 'react';\r\nimport type { SeoProps, UseSeoReturn } from './types';\r\nimport {\r\n setTitle,\r\n setMetaTag,\r\n setCanonical,\r\n setOpenGraphTags,\r\n setTwitterCardTags,\r\n setCustomMetaTags,\r\n injectJsonLd,\r\n formatKeywords,\r\n removeCanonical,\r\n removeJsonLd,\r\n} from './utils';\r\n\r\nconst JSON_LD_ID = 'react-client-seo-jsonld';\r\n\r\n/**\r\n * useSeo Hook\r\n * \r\n * Hook-based API for managing SEO meta tags.\r\n * \r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const { updateSeo } = useSeo();\r\n * \r\n * useEffect(() => {\r\n * updateSeo({\r\n * title: 'My Page',\r\n * description: 'Page description',\r\n * });\r\n * }, []);\r\n * \r\n * return <div>Content</div>;\r\n * }\r\n * ```\r\n */\r\nexport function useSeo(): UseSeoReturn {\r\n const updateSeo = useCallback((props: SeoProps) => {\r\n const {\r\n title,\r\n description,\r\n keywords,\r\n canonical,\r\n ogImage,\r\n ogType,\r\n ogUrl,\r\n ogTitle,\r\n ogDescription,\r\n ogSiteName,\r\n ogLocale,\r\n twitterCard,\r\n twitterSite,\r\n twitterCreator,\r\n twitterTitle,\r\n twitterDescription,\r\n twitterImage,\r\n twitterImageAlt,\r\n jsonLd,\r\n customMeta,\r\n openGraph,\r\n twitter,\r\n } = props;\r\n\r\n // Set title\r\n if (title) {\r\n setTitle(title);\r\n }\r\n\r\n // Set description\r\n if (description) {\r\n setMetaTag('description', description);\r\n }\r\n\r\n // Set keywords\r\n const keywordsStr = formatKeywords(keywords);\r\n if (keywordsStr) {\r\n setMetaTag('keywords', keywordsStr);\r\n }\r\n\r\n // Set canonical URL\r\n if (canonical) {\r\n setCanonical(canonical);\r\n }\r\n\r\n // Build Open Graph object\r\n const ogData: any = {\r\n ...openGraph,\r\n };\r\n\r\n if (ogTitle || title) {\r\n ogData.title = ogTitle || title;\r\n }\r\n if (ogDescription || description) {\r\n ogData.description = ogDescription || description;\r\n }\r\n if (ogType) {\r\n ogData.type = ogType;\r\n }\r\n if (ogUrl || canonical) {\r\n ogData.url = ogUrl || canonical;\r\n }\r\n if (ogImage) {\r\n ogData.image = ogImage;\r\n }\r\n if (ogSiteName) {\r\n ogData.siteName = ogSiteName;\r\n }\r\n if (ogLocale) {\r\n ogData.locale = ogLocale;\r\n }\r\n\r\n if (Object.keys(ogData).length > 0) {\r\n setOpenGraphTags(ogData);\r\n }\r\n\r\n // Build Twitter Card object\r\n const twitterData: any = {\r\n ...twitter,\r\n };\r\n\r\n if (twitterCard) {\r\n twitterData.card = twitterCard;\r\n }\r\n if (twitterSite) {\r\n twitterData.site = twitterSite;\r\n }\r\n if (twitterCreator) {\r\n twitterData.creator = twitterCreator;\r\n }\r\n if (twitterTitle || title) {\r\n twitterData.title = twitterTitle || title;\r\n }\r\n if (twitterDescription || description) {\r\n twitterData.description = twitterDescription || description;\r\n }\r\n if (twitterImage || ogImage) {\r\n twitterData.image = twitterImage || ogImage;\r\n }\r\n if (twitterImageAlt) {\r\n twitterData.imageAlt = twitterImageAlt;\r\n }\r\n\r\n if (Object.keys(twitterData).length > 0) {\r\n setTwitterCardTags(twitterData);\r\n }\r\n\r\n // Set custom meta tags\r\n if (customMeta) {\r\n setCustomMetaTags(customMeta);\r\n }\r\n\r\n // Inject JSON-LD\r\n if (jsonLd) {\r\n injectJsonLd(jsonLd, JSON_LD_ID);\r\n }\r\n }, []);\r\n\r\n const clearSeo = useCallback(() => {\r\n // Note: We don't clear title, description, keywords as they might be set by other means\r\n // Only clear what we manage\r\n removeCanonical();\r\n removeJsonLd(JSON_LD_ID);\r\n }, []);\r\n\r\n return {\r\n updateSeo,\r\n clearSeo,\r\n };\r\n}\r\n\r\n"],"names":["getOrCreateMetaTag","attribute","value","selector","element","setMetaTag","name","content","setMetaProperty","property","setTitle","title","setCanonical","url","link","removeCanonical","setOpenGraphTags","og","key","setTwitterCardTags","twitter","setCustomMetaTags","customMeta","meta","charsetElement","injectJsonLd","data","id","script","existing","removeJsonLd","formatKeywords","keywords","JSON_LD_ID","Seo","description","canonical","ogImage","ogType","ogUrl","ogTitle","ogDescription","ogSiteName","ogLocale","twitterCard","twitterSite","twitterCreator","twitterTitle","twitterDescription","twitterImage","twitterImageAlt","jsonLd","openGraph","useEffect","keywordsStr","ogData","twitterData","cleanupJsonLd","useSeo","updateSeo","useCallback","props","clearSeo"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { Seo } from './Seo';
2
+ export { useSeo } from './useSeo';
3
+ export type { SeoProps, OpenGraphMeta, TwitterCardMeta, CustomMeta, JsonLd, UseSeoReturn, } from './types';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EACV,QAAQ,EACR,aAAa,EACb,eAAe,EACf,UAAU,EACV,MAAM,EACN,YAAY,GACb,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,197 @@
1
+ import { useEffect as M, useCallback as _ } from "react";
2
+ function W(e, t) {
3
+ const i = `meta[${e}="${t}"]`;
4
+ let n = document.querySelector(i);
5
+ return n || (n = document.createElement("meta"), n.setAttribute(e, t), document.head.appendChild(n)), n;
6
+ }
7
+ function c(e, t) {
8
+ if (!t) return;
9
+ W("name", e).setAttribute("content", t);
10
+ }
11
+ function a(e, t) {
12
+ if (t == null || t === "") return;
13
+ W("property", e).setAttribute("content", String(t));
14
+ }
15
+ function $(e) {
16
+ e && (document.title = e);
17
+ }
18
+ function x(e) {
19
+ if (!e) return;
20
+ let t = document.querySelector('link[rel="canonical"]');
21
+ t || (t = document.createElement("link"), t.setAttribute("rel", "canonical"), document.head.appendChild(t)), t.setAttribute("href", e);
22
+ }
23
+ function P() {
24
+ const e = document.querySelector('link[rel="canonical"]');
25
+ e && e.remove();
26
+ }
27
+ function B(e) {
28
+ e && (e.title && a("og:title", e.title), e.description && a("og:description", e.description), e.type && a("og:type", e.type), e.url && a("og:url", e.url), e.image && a("og:image", e.image), e.imageWidth && a("og:image:width", e.imageWidth), e.imageHeight && a("og:image:height", e.imageHeight), e.imageAlt && a("og:image:alt", e.imageAlt), e.siteName && a("og:site_name", e.siteName), e.locale && a("og:locale", e.locale), Object.keys(e).forEach((t) => {
29
+ if (t.startsWith("og:") && t !== "og:title" && t !== "og:description" && t !== "og:type" && t !== "og:url" && t !== "og:image" && t !== "og:imageWidth" && t !== "og:imageHeight" && t !== "og:imageAlt" && t !== "og:siteName" && t !== "og:locale") {
30
+ const i = e[t];
31
+ i != null && i !== "" && a(t, i);
32
+ }
33
+ }));
34
+ }
35
+ function H(e) {
36
+ e && (e.card && c("twitter:card", e.card), e.site && c("twitter:site", e.site), e.creator && c("twitter:creator", e.creator), e.title && c("twitter:title", e.title), e.description && c("twitter:description", e.description), e.image && c("twitter:image", e.image), e.imageAlt && c("twitter:image:alt", e.imageAlt), Object.keys(e).forEach((t) => {
37
+ if (t.startsWith("twitter:") && t !== "twitter:card" && t !== "twitter:site" && t !== "twitter:creator" && t !== "twitter:title" && t !== "twitter:description" && t !== "twitter:image" && t !== "twitter:imageAlt") {
38
+ const i = e[t];
39
+ i != null && i !== "" && c(t, String(i));
40
+ }
41
+ }));
42
+ }
43
+ function L(e) {
44
+ !e || !Array.isArray(e) || e.forEach((t) => {
45
+ if (!t.content) return;
46
+ let i = null;
47
+ if (t.name)
48
+ i = W("name", t.name);
49
+ else if (t.property)
50
+ i = W("property", t.property);
51
+ else if (t.httpEquiv) {
52
+ const n = `meta[http-equiv="${t.httpEquiv}"]`;
53
+ i = document.querySelector(n), i || (i = document.createElement("meta"), i.setAttribute("http-equiv", t.httpEquiv), document.head.appendChild(i));
54
+ } else if (t.charset) {
55
+ let n = document.querySelector("meta[charset]");
56
+ n || (n = document.createElement("meta"), n.setAttribute("charset", t.charset), document.head.insertBefore(n, document.head.firstChild));
57
+ return;
58
+ }
59
+ i && i.setAttribute("content", t.content);
60
+ });
61
+ }
62
+ function G(e, t) {
63
+ const i = document.createElement("script");
64
+ i.type = "application/ld+json";
65
+ {
66
+ i.id = t;
67
+ const n = document.getElementById(t);
68
+ n && n.remove();
69
+ }
70
+ return i.textContent = JSON.stringify(e), document.head.appendChild(i), () => {
71
+ i.remove();
72
+ };
73
+ }
74
+ function z(e) {
75
+ const t = document.getElementById(e);
76
+ t && t.getAttribute("type") === "application/ld+json" && t.remove();
77
+ }
78
+ function K(e) {
79
+ return e ? Array.isArray(e) ? e.join(", ") : e : "";
80
+ }
81
+ const F = "react-client-seo-jsonld";
82
+ function R({
83
+ title: e,
84
+ description: t,
85
+ keywords: i,
86
+ canonical: n,
87
+ ogImage: r,
88
+ ogType: C,
89
+ ogUrl: u,
90
+ ogTitle: m,
91
+ ogDescription: d,
92
+ ogSiteName: p,
93
+ ogLocale: h,
94
+ twitterCard: g,
95
+ twitterSite: A,
96
+ twitterCreator: b,
97
+ twitterTitle: v,
98
+ twitterDescription: E,
99
+ twitterImage: S,
100
+ twitterImageAlt: j,
101
+ jsonLd: y,
102
+ customMeta: O,
103
+ openGraph: N,
104
+ twitter: D
105
+ }) {
106
+ return M(() => {
107
+ e && $(e), t && c("description", t);
108
+ const J = K(i);
109
+ J && c("keywords", J), n && x(n);
110
+ const f = {
111
+ ...N
112
+ };
113
+ (m || e) && (f.title = m || e), (d || t) && (f.description = d || t), C && (f.type = C), (u || n) && (f.url = u || n), r && (f.image = r), p && (f.siteName = p), h && (f.locale = h), Object.keys(f).length > 0 && B(f);
114
+ const o = {
115
+ ...D
116
+ };
117
+ g && (o.card = g), A && (o.site = A), b && (o.creator = b), (v || e) && (o.title = v || e), (E || t) && (o.description = E || t), (S || r) && (o.image = S || r), j && (o.imageAlt = j), Object.keys(o).length > 0 && H(o), O && L(O);
118
+ let q = null;
119
+ return y && (q = G(y, F)), () => {
120
+ q && q();
121
+ };
122
+ }, [
123
+ e,
124
+ t,
125
+ i,
126
+ n,
127
+ r,
128
+ C,
129
+ u,
130
+ m,
131
+ d,
132
+ p,
133
+ h,
134
+ g,
135
+ A,
136
+ b,
137
+ v,
138
+ E,
139
+ S,
140
+ j,
141
+ y,
142
+ O,
143
+ N,
144
+ D
145
+ ]), null;
146
+ }
147
+ const T = "react-client-seo-jsonld";
148
+ function V() {
149
+ const e = _((i) => {
150
+ const {
151
+ title: n,
152
+ description: r,
153
+ keywords: C,
154
+ canonical: u,
155
+ ogImage: m,
156
+ ogType: d,
157
+ ogUrl: p,
158
+ ogTitle: h,
159
+ ogDescription: g,
160
+ ogSiteName: A,
161
+ ogLocale: b,
162
+ twitterCard: v,
163
+ twitterSite: E,
164
+ twitterCreator: S,
165
+ twitterTitle: j,
166
+ twitterDescription: y,
167
+ twitterImage: O,
168
+ twitterImageAlt: N,
169
+ jsonLd: D,
170
+ customMeta: J,
171
+ openGraph: f,
172
+ twitter: o
173
+ } = i;
174
+ n && $(n), r && c("description", r);
175
+ const q = K(C);
176
+ q && c("keywords", q), u && x(u);
177
+ const l = {
178
+ ...f
179
+ };
180
+ (h || n) && (l.title = h || n), (g || r) && (l.description = g || r), d && (l.type = d), (p || u) && (l.url = p || u), m && (l.image = m), A && (l.siteName = A), b && (l.locale = b), Object.keys(l).length > 0 && B(l);
181
+ const s = {
182
+ ...o
183
+ };
184
+ v && (s.card = v), E && (s.site = E), S && (s.creator = S), (j || n) && (s.title = j || n), (y || r) && (s.description = y || r), (O || m) && (s.image = O || m), N && (s.imageAlt = N), Object.keys(s).length > 0 && H(s), J && L(J), D && G(D, T);
185
+ }, []), t = _(() => {
186
+ P(), z(T);
187
+ }, []);
188
+ return {
189
+ updateSeo: e,
190
+ clearSeo: t
191
+ };
192
+ }
193
+ export {
194
+ R as Seo,
195
+ V as useSeo
196
+ };
197
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/utils.ts","../src/Seo.tsx","../src/useSeo.ts"],"sourcesContent":["import type { CustomMeta, JsonLd, OpenGraphMeta, TwitterCardMeta } from './types';\r\n\r\n/**\r\n * Get or create a meta tag element\r\n */\r\nfunction getOrCreateMetaTag(attribute: 'name' | 'property', value: string): HTMLMetaElement {\r\n const selector = `meta[${attribute}=\"${value}\"]`;\r\n let element = document.querySelector<HTMLMetaElement>(selector);\r\n\r\n if (!element) {\r\n element = document.createElement('meta');\r\n element.setAttribute(attribute, value);\r\n document.head.appendChild(element);\r\n }\r\n\r\n return element;\r\n}\r\n\r\n/**\r\n * Set a meta tag by name\r\n */\r\nexport function setMetaTag(name: string, content: string): void {\r\n if (!content) return;\r\n\r\n const element = getOrCreateMetaTag('name', name);\r\n element.setAttribute('content', content);\r\n}\r\n\r\n/**\r\n * Set a meta tag by property (for Open Graph)\r\n */\r\nexport function setMetaProperty(property: string, content: string | number): void {\r\n if (content === undefined || content === null || content === '') return;\r\n\r\n const element = getOrCreateMetaTag('property', property);\r\n element.setAttribute('content', String(content));\r\n}\r\n\r\n/**\r\n * Set the page title\r\n */\r\nexport function setTitle(title: string): void {\r\n if (!title) return;\r\n document.title = title;\r\n}\r\n\r\n/**\r\n * Set canonical URL\r\n */\r\nexport function setCanonical(url: string): void {\r\n if (!url) return;\r\n\r\n let link = document.querySelector<HTMLLinkElement>('link[rel=\"canonical\"]');\r\n if (!link) {\r\n link = document.createElement('link');\r\n link.setAttribute('rel', 'canonical');\r\n document.head.appendChild(link);\r\n }\r\n link.setAttribute('href', url);\r\n}\r\n\r\n/**\r\n * Remove canonical URL\r\n */\r\nexport function removeCanonical(): void {\r\n const link = document.querySelector<HTMLLinkElement>('link[rel=\"canonical\"]');\r\n if (link) {\r\n link.remove();\r\n }\r\n}\r\n\r\n/**\r\n * Set Open Graph meta tags\r\n */\r\nexport function setOpenGraphTags(og: OpenGraphMeta): void {\r\n if (!og) return;\r\n\r\n // Standard Open Graph properties\r\n if (og.title) setMetaProperty('og:title', og.title);\r\n if (og.description) setMetaProperty('og:description', og.description);\r\n if (og.type) setMetaProperty('og:type', og.type);\r\n if (og.url) setMetaProperty('og:url', og.url);\r\n if (og.image) setMetaProperty('og:image', og.image);\r\n if (og.imageWidth) setMetaProperty('og:image:width', og.imageWidth);\r\n if (og.imageHeight) setMetaProperty('og:image:height', og.imageHeight);\r\n if (og.imageAlt) setMetaProperty('og:image:alt', og.imageAlt);\r\n if (og.siteName) setMetaProperty('og:site_name', og.siteName);\r\n if (og.locale) setMetaProperty('og:locale', og.locale);\r\n\r\n // Handle additional custom og:* properties\r\n Object.keys(og).forEach((key) => {\r\n if (key.startsWith('og:') && key !== 'og:title' && key !== 'og:description' && \r\n key !== 'og:type' && key !== 'og:url' && key !== 'og:image' && \r\n key !== 'og:imageWidth' && key !== 'og:imageHeight' && \r\n key !== 'og:imageAlt' && key !== 'og:siteName' && key !== 'og:locale') {\r\n const value = og[key as keyof OpenGraphMeta];\r\n if (value !== undefined && value !== null && value !== '') {\r\n setMetaProperty(key, value as string | number);\r\n }\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Set Twitter Card meta tags\r\n */\r\nexport function setTwitterCardTags(twitter: TwitterCardMeta): void {\r\n if (!twitter) return;\r\n\r\n if (twitter.card) setMetaTag('twitter:card', twitter.card);\r\n if (twitter.site) setMetaTag('twitter:site', twitter.site);\r\n if (twitter.creator) setMetaTag('twitter:creator', twitter.creator);\r\n if (twitter.title) setMetaTag('twitter:title', twitter.title);\r\n if (twitter.description) setMetaTag('twitter:description', twitter.description);\r\n if (twitter.image) setMetaTag('twitter:image', twitter.image);\r\n if (twitter.imageAlt) setMetaTag('twitter:image:alt', twitter.imageAlt);\r\n\r\n // Handle additional custom twitter:* properties\r\n Object.keys(twitter).forEach((key) => {\r\n if (key.startsWith('twitter:') && key !== 'twitter:card' && key !== 'twitter:site' && \r\n key !== 'twitter:creator' && key !== 'twitter:title' && \r\n key !== 'twitter:description' && key !== 'twitter:image' && \r\n key !== 'twitter:imageAlt') {\r\n const value = twitter[key as keyof TwitterCardMeta];\r\n if (value !== undefined && value !== null && value !== '') {\r\n setMetaTag(key, String(value));\r\n }\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Set custom meta tags\r\n */\r\nexport function setCustomMetaTags(customMeta: CustomMeta[]): void {\r\n if (!customMeta || !Array.isArray(customMeta)) return;\r\n\r\n customMeta.forEach((meta) => {\r\n if (!meta.content) return;\r\n\r\n let element: HTMLMetaElement | null = null;\r\n\r\n if (meta.name) {\r\n element = getOrCreateMetaTag('name', meta.name);\r\n } else if (meta.property) {\r\n element = getOrCreateMetaTag('property', meta.property);\r\n } else if (meta.httpEquiv) {\r\n const selector = `meta[http-equiv=\"${meta.httpEquiv}\"]`;\r\n element = document.querySelector<HTMLMetaElement>(selector);\r\n if (!element) {\r\n element = document.createElement('meta');\r\n element.setAttribute('http-equiv', meta.httpEquiv);\r\n document.head.appendChild(element);\r\n }\r\n } else if (meta.charset) {\r\n let charsetElement = document.querySelector<HTMLMetaElement>('meta[charset]');\r\n if (!charsetElement) {\r\n charsetElement = document.createElement('meta');\r\n charsetElement.setAttribute('charset', meta.charset);\r\n document.head.insertBefore(charsetElement, document.head.firstChild);\r\n }\r\n return;\r\n }\r\n\r\n if (element) {\r\n element.setAttribute('content', meta.content);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Inject JSON-LD structured data\r\n */\r\nexport function injectJsonLd(data: JsonLd, id?: string): () => void {\r\n const script = document.createElement('script');\r\n script.type = 'application/ld+json';\r\n if (id) {\r\n script.id = id;\r\n // Remove existing script with same id\r\n const existing = document.getElementById(id);\r\n if (existing) {\r\n existing.remove();\r\n }\r\n }\r\n script.textContent = JSON.stringify(data);\r\n document.head.appendChild(script);\r\n\r\n // Return cleanup function\r\n return () => {\r\n script.remove();\r\n };\r\n}\r\n\r\n/**\r\n * Remove JSON-LD script by id\r\n */\r\nexport function removeJsonLd(id: string): void {\r\n const script = document.getElementById(id);\r\n if (script && script.getAttribute('type') === 'application/ld+json') {\r\n script.remove();\r\n }\r\n}\r\n\r\n/**\r\n * Format keywords as string\r\n */\r\nexport function formatKeywords(keywords: string | string[] | undefined): string {\r\n if (!keywords) return '';\r\n if (Array.isArray(keywords)) {\r\n return keywords.join(', ');\r\n }\r\n return keywords;\r\n}\r\n\r\n","import { useEffect } from 'react';\r\nimport type { SeoProps } from './types';\r\nimport {\r\n setTitle,\r\n setMetaTag,\r\n setCanonical,\r\n setOpenGraphTags,\r\n setTwitterCardTags,\r\n setCustomMetaTags,\r\n injectJsonLd,\r\n formatKeywords,\r\n} from './utils';\r\n\r\nconst JSON_LD_ID = 'react-client-seo-jsonld';\r\n\r\n/**\r\n * SEO Component\r\n * \r\n * Renders and updates SEO meta tags in the document head.\r\n * Returns null (no UI rendering).\r\n * \r\n * @example\r\n * ```tsx\r\n * <Seo\r\n * title=\"My Page Title\"\r\n * description=\"Page description\"\r\n * keywords={['react', 'seo']}\r\n * canonical=\"https://example.com/page\"\r\n * ogImage=\"https://example.com/image.jpg\"\r\n * jsonLd={{ \"@context\": \"https://schema.org\", \"@type\": \"WebPage\" }}\r\n * />\r\n * ```\r\n */\r\nexport function Seo({\r\n title,\r\n description,\r\n keywords,\r\n canonical,\r\n ogImage,\r\n ogType,\r\n ogUrl,\r\n ogTitle,\r\n ogDescription,\r\n ogSiteName,\r\n ogLocale,\r\n twitterCard,\r\n twitterSite,\r\n twitterCreator,\r\n twitterTitle,\r\n twitterDescription,\r\n twitterImage,\r\n twitterImageAlt,\r\n jsonLd,\r\n customMeta,\r\n openGraph,\r\n twitter,\r\n}: SeoProps) {\r\n useEffect(() => {\r\n // Set title\r\n if (title) {\r\n setTitle(title);\r\n }\r\n\r\n // Set description\r\n if (description) {\r\n setMetaTag('description', description);\r\n }\r\n\r\n // Set keywords\r\n const keywordsStr = formatKeywords(keywords);\r\n if (keywordsStr) {\r\n setMetaTag('keywords', keywordsStr);\r\n }\r\n\r\n // Set canonical URL\r\n if (canonical) {\r\n setCanonical(canonical);\r\n }\r\n\r\n // Build Open Graph object\r\n const ogData: any = {\r\n ...openGraph,\r\n };\r\n\r\n if (ogTitle || title) {\r\n ogData.title = ogTitle || title;\r\n }\r\n if (ogDescription || description) {\r\n ogData.description = ogDescription || description;\r\n }\r\n if (ogType) {\r\n ogData.type = ogType;\r\n }\r\n if (ogUrl || canonical) {\r\n ogData.url = ogUrl || canonical;\r\n }\r\n if (ogImage) {\r\n ogData.image = ogImage;\r\n }\r\n if (ogSiteName) {\r\n ogData.siteName = ogSiteName;\r\n }\r\n if (ogLocale) {\r\n ogData.locale = ogLocale;\r\n }\r\n\r\n if (Object.keys(ogData).length > 0) {\r\n setOpenGraphTags(ogData);\r\n }\r\n\r\n // Build Twitter Card object\r\n const twitterData: any = {\r\n ...twitter,\r\n };\r\n\r\n if (twitterCard) {\r\n twitterData.card = twitterCard;\r\n }\r\n if (twitterSite) {\r\n twitterData.site = twitterSite;\r\n }\r\n if (twitterCreator) {\r\n twitterData.creator = twitterCreator;\r\n }\r\n if (twitterTitle || title) {\r\n twitterData.title = twitterTitle || title;\r\n }\r\n if (twitterDescription || description) {\r\n twitterData.description = twitterDescription || description;\r\n }\r\n if (twitterImage || ogImage) {\r\n twitterData.image = twitterImage || ogImage;\r\n }\r\n if (twitterImageAlt) {\r\n twitterData.imageAlt = twitterImageAlt;\r\n }\r\n\r\n if (Object.keys(twitterData).length > 0) {\r\n setTwitterCardTags(twitterData);\r\n }\r\n\r\n // Set custom meta tags\r\n if (customMeta) {\r\n setCustomMetaTags(customMeta);\r\n }\r\n\r\n // Inject JSON-LD\r\n let cleanupJsonLd: (() => void) | null = null;\r\n if (jsonLd) {\r\n cleanupJsonLd = injectJsonLd(jsonLd, JSON_LD_ID);\r\n }\r\n\r\n // Cleanup function\r\n return () => {\r\n if (cleanupJsonLd) {\r\n cleanupJsonLd();\r\n }\r\n };\r\n }, [\r\n title,\r\n description,\r\n keywords,\r\n canonical,\r\n ogImage,\r\n ogType,\r\n ogUrl,\r\n ogTitle,\r\n ogDescription,\r\n ogSiteName,\r\n ogLocale,\r\n twitterCard,\r\n twitterSite,\r\n twitterCreator,\r\n twitterTitle,\r\n twitterDescription,\r\n twitterImage,\r\n twitterImageAlt,\r\n jsonLd,\r\n customMeta,\r\n openGraph,\r\n twitter,\r\n ]);\r\n\r\n return null;\r\n}\r\n\r\n","import { useCallback } from 'react';\r\nimport type { SeoProps, UseSeoReturn } from './types';\r\nimport {\r\n setTitle,\r\n setMetaTag,\r\n setCanonical,\r\n setOpenGraphTags,\r\n setTwitterCardTags,\r\n setCustomMetaTags,\r\n injectJsonLd,\r\n formatKeywords,\r\n removeCanonical,\r\n removeJsonLd,\r\n} from './utils';\r\n\r\nconst JSON_LD_ID = 'react-client-seo-jsonld';\r\n\r\n/**\r\n * useSeo Hook\r\n * \r\n * Hook-based API for managing SEO meta tags.\r\n * \r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const { updateSeo } = useSeo();\r\n * \r\n * useEffect(() => {\r\n * updateSeo({\r\n * title: 'My Page',\r\n * description: 'Page description',\r\n * });\r\n * }, []);\r\n * \r\n * return <div>Content</div>;\r\n * }\r\n * ```\r\n */\r\nexport function useSeo(): UseSeoReturn {\r\n const updateSeo = useCallback((props: SeoProps) => {\r\n const {\r\n title,\r\n description,\r\n keywords,\r\n canonical,\r\n ogImage,\r\n ogType,\r\n ogUrl,\r\n ogTitle,\r\n ogDescription,\r\n ogSiteName,\r\n ogLocale,\r\n twitterCard,\r\n twitterSite,\r\n twitterCreator,\r\n twitterTitle,\r\n twitterDescription,\r\n twitterImage,\r\n twitterImageAlt,\r\n jsonLd,\r\n customMeta,\r\n openGraph,\r\n twitter,\r\n } = props;\r\n\r\n // Set title\r\n if (title) {\r\n setTitle(title);\r\n }\r\n\r\n // Set description\r\n if (description) {\r\n setMetaTag('description', description);\r\n }\r\n\r\n // Set keywords\r\n const keywordsStr = formatKeywords(keywords);\r\n if (keywordsStr) {\r\n setMetaTag('keywords', keywordsStr);\r\n }\r\n\r\n // Set canonical URL\r\n if (canonical) {\r\n setCanonical(canonical);\r\n }\r\n\r\n // Build Open Graph object\r\n const ogData: any = {\r\n ...openGraph,\r\n };\r\n\r\n if (ogTitle || title) {\r\n ogData.title = ogTitle || title;\r\n }\r\n if (ogDescription || description) {\r\n ogData.description = ogDescription || description;\r\n }\r\n if (ogType) {\r\n ogData.type = ogType;\r\n }\r\n if (ogUrl || canonical) {\r\n ogData.url = ogUrl || canonical;\r\n }\r\n if (ogImage) {\r\n ogData.image = ogImage;\r\n }\r\n if (ogSiteName) {\r\n ogData.siteName = ogSiteName;\r\n }\r\n if (ogLocale) {\r\n ogData.locale = ogLocale;\r\n }\r\n\r\n if (Object.keys(ogData).length > 0) {\r\n setOpenGraphTags(ogData);\r\n }\r\n\r\n // Build Twitter Card object\r\n const twitterData: any = {\r\n ...twitter,\r\n };\r\n\r\n if (twitterCard) {\r\n twitterData.card = twitterCard;\r\n }\r\n if (twitterSite) {\r\n twitterData.site = twitterSite;\r\n }\r\n if (twitterCreator) {\r\n twitterData.creator = twitterCreator;\r\n }\r\n if (twitterTitle || title) {\r\n twitterData.title = twitterTitle || title;\r\n }\r\n if (twitterDescription || description) {\r\n twitterData.description = twitterDescription || description;\r\n }\r\n if (twitterImage || ogImage) {\r\n twitterData.image = twitterImage || ogImage;\r\n }\r\n if (twitterImageAlt) {\r\n twitterData.imageAlt = twitterImageAlt;\r\n }\r\n\r\n if (Object.keys(twitterData).length > 0) {\r\n setTwitterCardTags(twitterData);\r\n }\r\n\r\n // Set custom meta tags\r\n if (customMeta) {\r\n setCustomMetaTags(customMeta);\r\n }\r\n\r\n // Inject JSON-LD\r\n if (jsonLd) {\r\n injectJsonLd(jsonLd, JSON_LD_ID);\r\n }\r\n }, []);\r\n\r\n const clearSeo = useCallback(() => {\r\n // Note: We don't clear title, description, keywords as they might be set by other means\r\n // Only clear what we manage\r\n removeCanonical();\r\n removeJsonLd(JSON_LD_ID);\r\n }, []);\r\n\r\n return {\r\n updateSeo,\r\n clearSeo,\r\n };\r\n}\r\n\r\n"],"names":["getOrCreateMetaTag","attribute","value","selector","element","setMetaTag","name","content","setMetaProperty","property","setTitle","title","setCanonical","url","link","removeCanonical","setOpenGraphTags","og","key","setTwitterCardTags","twitter","setCustomMetaTags","customMeta","meta","charsetElement","injectJsonLd","data","id","script","existing","removeJsonLd","formatKeywords","keywords","JSON_LD_ID","Seo","description","canonical","ogImage","ogType","ogUrl","ogTitle","ogDescription","ogSiteName","ogLocale","twitterCard","twitterSite","twitterCreator","twitterTitle","twitterDescription","twitterImage","twitterImageAlt","jsonLd","openGraph","useEffect","keywordsStr","ogData","twitterData","cleanupJsonLd","useSeo","updateSeo","useCallback","props","clearSeo"],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Open Graph metadata
3
+ */
4
+ export interface OpenGraphMeta {
5
+ title?: string;
6
+ description?: string;
7
+ type?: string;
8
+ url?: string;
9
+ image?: string;
10
+ imageWidth?: string | number;
11
+ imageHeight?: string | number;
12
+ imageAlt?: string;
13
+ siteName?: string;
14
+ locale?: string;
15
+ [key: `og:${string}`]: string | number | undefined;
16
+ }
17
+ /**
18
+ * Twitter Card metadata
19
+ */
20
+ export interface TwitterCardMeta {
21
+ card?: 'summary' | 'summary_large_image' | 'app' | 'player';
22
+ site?: string;
23
+ creator?: string;
24
+ title?: string;
25
+ description?: string;
26
+ image?: string;
27
+ imageAlt?: string;
28
+ [key: `twitter:${string}`]: string | number | undefined;
29
+ }
30
+ /**
31
+ * Custom meta tag
32
+ */
33
+ export interface CustomMeta {
34
+ name?: string;
35
+ property?: string;
36
+ content: string;
37
+ httpEquiv?: string;
38
+ charset?: string;
39
+ }
40
+ /**
41
+ * JSON-LD structured data
42
+ */
43
+ export type JsonLd = Record<string, any> | Array<Record<string, any>>;
44
+ /**
45
+ * SEO component props
46
+ */
47
+ export interface SeoProps {
48
+ /** Page title */
49
+ title?: string;
50
+ /** Meta description */
51
+ description?: string;
52
+ /** Meta keywords (comma-separated string or array) */
53
+ keywords?: string | string[];
54
+ /** Canonical URL */
55
+ canonical?: string;
56
+ /** Open Graph metadata */
57
+ ogImage?: string;
58
+ ogType?: string;
59
+ ogUrl?: string;
60
+ ogTitle?: string;
61
+ ogDescription?: string;
62
+ ogSiteName?: string;
63
+ ogLocale?: string;
64
+ /** Twitter Card metadata */
65
+ twitterCard?: TwitterCardMeta['card'];
66
+ twitterSite?: string;
67
+ twitterCreator?: string;
68
+ twitterTitle?: string;
69
+ twitterDescription?: string;
70
+ twitterImage?: string;
71
+ twitterImageAlt?: string;
72
+ /** JSON-LD structured data */
73
+ jsonLd?: JsonLd;
74
+ /** Custom meta tags */
75
+ customMeta?: CustomMeta[];
76
+ /** Additional Open Graph properties */
77
+ openGraph?: OpenGraphMeta;
78
+ /** Additional Twitter Card properties */
79
+ twitter?: TwitterCardMeta;
80
+ }
81
+ /**
82
+ * Hook return type
83
+ */
84
+ export interface UseSeoReturn {
85
+ updateSeo: (props: SeoProps) => void;
86
+ clearSeo: () => void;
87
+ }
88
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { UseSeoReturn } from './types';
2
+
3
+ /**
4
+ * useSeo Hook
5
+ *
6
+ * Hook-based API for managing SEO meta tags.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * function MyComponent() {
11
+ * const { updateSeo } = useSeo();
12
+ *
13
+ * useEffect(() => {
14
+ * updateSeo({
15
+ * title: 'My Page',
16
+ * description: 'Page description',
17
+ * });
18
+ * }, []);
19
+ *
20
+ * return <div>Content</div>;
21
+ * }
22
+ * ```
23
+ */
24
+ export declare function useSeo(): UseSeoReturn;
25
+ //# sourceMappingURL=useSeo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSeo.d.ts","sourceRoot":"","sources":["../src/useSeo.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,SAAS,CAAC;AAgBtD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,MAAM,IAAI,YAAY,CAoIrC"}
@@ -0,0 +1,47 @@
1
+ import { CustomMeta, JsonLd, OpenGraphMeta, TwitterCardMeta } from './types';
2
+
3
+ /**
4
+ * Set a meta tag by name
5
+ */
6
+ export declare function setMetaTag(name: string, content: string): void;
7
+ /**
8
+ * Set a meta tag by property (for Open Graph)
9
+ */
10
+ export declare function setMetaProperty(property: string, content: string | number): void;
11
+ /**
12
+ * Set the page title
13
+ */
14
+ export declare function setTitle(title: string): void;
15
+ /**
16
+ * Set canonical URL
17
+ */
18
+ export declare function setCanonical(url: string): void;
19
+ /**
20
+ * Remove canonical URL
21
+ */
22
+ export declare function removeCanonical(): void;
23
+ /**
24
+ * Set Open Graph meta tags
25
+ */
26
+ export declare function setOpenGraphTags(og: OpenGraphMeta): void;
27
+ /**
28
+ * Set Twitter Card meta tags
29
+ */
30
+ export declare function setTwitterCardTags(twitter: TwitterCardMeta): void;
31
+ /**
32
+ * Set custom meta tags
33
+ */
34
+ export declare function setCustomMetaTags(customMeta: CustomMeta[]): void;
35
+ /**
36
+ * Inject JSON-LD structured data
37
+ */
38
+ export declare function injectJsonLd(data: JsonLd, id?: string): () => void;
39
+ /**
40
+ * Remove JSON-LD script by id
41
+ */
42
+ export declare function removeJsonLd(id: string): void;
43
+ /**
44
+ * Format keywords as string
45
+ */
46
+ export declare function formatKeywords(keywords: string | string[] | undefined): string;
47
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAkBlF;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAK9D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKhF;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAG5C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAU9C;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAKtC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,aAAa,GAAG,IAAI,CA2BxD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAuBjE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,IAAI,CAkChE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,IAAI,CAkBlE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAK7C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,CAM9E"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "react-client-seo",
3
+ "version": "1.0.0",
4
+ "description": "Lightweight client-side SEO renderer for React and Vite apps",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "dev": "vite build --watch --mode development",
14
+ "build": "tsc && vite build",
15
+ "preview": "vite preview",
16
+ "prepare": "npm run build"
17
+ },
18
+ "keywords": [
19
+ "react",
20
+ "seo",
21
+ "meta-tags",
22
+ "open-graph",
23
+ "twitter-cards",
24
+ "json-ld",
25
+ "vite",
26
+ "client-side",
27
+ "typescript"
28
+ ],
29
+ "author": "Rainard Joseph",
30
+ "license": "MIT",
31
+ "peerDependencies": {
32
+ "react": ">=17.0.0",
33
+ "react-dom": ">=17.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/react": "^18.2.0",
37
+ "@types/react-dom": "^18.2.0",
38
+ "@vitejs/plugin-react": "^4.2.0",
39
+ "react": "^18.2.0",
40
+ "react-dom": "^18.2.0",
41
+ "typescript": "^5.2.0",
42
+ "vite": "^5.0.0",
43
+ "vite-plugin-dts": "^3.6.0"
44
+ }
45
+ }
46
+