@geenius/seo 0.1.0 → 0.3.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/package.json +16 -3
- package/packages/convex/dist/index.d.ts +56 -0
- package/packages/convex/dist/index.js +133 -0
- package/packages/convex/dist/index.js.map +1 -0
- package/packages/react/README.md +1 -1
- package/packages/react/dist/index.d.ts +156 -0
- package/packages/react/dist/index.js +567 -0
- package/packages/react/dist/index.js.map +1 -0
- package/packages/react-css/README.md +1 -1
- package/packages/react-css/dist/index.cjs +571 -0
- package/packages/react-css/dist/index.cjs.map +1 -0
- package/packages/react-css/{src/seo.css → dist/index.css} +7 -153
- package/packages/react-css/dist/index.css.map +1 -0
- package/packages/react-css/dist/index.d.cts +53 -0
- package/packages/react-css/dist/index.d.ts +53 -0
- package/packages/react-css/dist/index.js +539 -0
- package/packages/react-css/dist/index.js.map +1 -0
- package/packages/shared/README.md +1 -1
- package/packages/shared/dist/index.d.ts +262 -0
- package/packages/shared/dist/index.js +381 -0
- package/packages/shared/dist/index.js.map +1 -0
- package/packages/solidjs/README.md +1 -1
- package/packages/solidjs/dist/index.d.ts +133 -0
- package/packages/solidjs/dist/index.js +416 -0
- package/packages/solidjs/dist/index.js.map +1 -0
- package/packages/solidjs-css/README.md +1 -1
- package/packages/solidjs-css/dist/index.cjs +399 -0
- package/packages/solidjs-css/dist/index.cjs.map +1 -0
- package/packages/solidjs-css/{src/seo.css → dist/index.css} +7 -153
- package/packages/solidjs-css/dist/index.css.map +1 -0
- package/packages/solidjs-css/dist/index.d.cts +53 -0
- package/packages/solidjs-css/dist/index.d.ts +53 -0
- package/packages/solidjs-css/dist/index.js +367 -0
- package/packages/solidjs-css/dist/index.js.map +1 -0
- package/.changeset/config.json +0 -11
- package/.github/CODEOWNERS +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -16
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/ci.yml +0 -23
- package/.github/workflows/release.yml +0 -29
- package/.nvmrc +0 -1
- package/.project/ACCOUNT.yaml +0 -4
- package/.project/IDEAS.yaml +0 -7
- package/.project/PROJECT.yaml +0 -11
- package/.project/ROADMAP.yaml +0 -15
- package/CODE_OF_CONDUCT.md +0 -16
- package/CONTRIBUTING.md +0 -26
- package/SECURITY.md +0 -15
- package/SUPPORT.md +0 -8
- package/packages/convex/package.json +0 -42
- package/packages/convex/src/functions.ts +0 -5
- package/packages/convex/src/mutations.ts +0 -83
- package/packages/convex/src/queries.ts +0 -57
- package/packages/convex/src/schema.ts +0 -23
- package/packages/convex/tsconfig.json +0 -19
- package/packages/convex/tsup.config.ts +0 -18
- package/packages/react/package.json +0 -49
- package/packages/react/src/components/ArticleJsonLd.tsx +0 -42
- package/packages/react/src/components/BreadcrumbsJsonLd.tsx +0 -24
- package/packages/react/src/components/MetaEditor.tsx +0 -147
- package/packages/react/src/components/SEOHead.tsx +0 -107
- package/packages/react/src/components/SEOPreview.tsx +0 -42
- package/packages/react/src/components/SEOScoreCard.tsx +0 -51
- package/packages/react/src/components/SitemapViewer.tsx +0 -36
- package/packages/react/src/components/index.ts +0 -7
- package/packages/react/src/hooks/index.ts +0 -4
- package/packages/react/src/hooks/useSEO.ts +0 -27
- package/packages/react/src/hooks/useSEOAdmin.ts +0 -42
- package/packages/react/src/hooks/useSEOScore.ts +0 -7
- package/packages/react/src/hooks/useSitemap.ts +0 -8
- package/packages/react/src/index.ts +0 -51
- package/packages/react/src/index.tsx +0 -11
- package/packages/react/src/pages/SEOAdminPage.tsx +0 -101
- package/packages/react/src/pages/SEOAnalyticsPage.tsx +0 -96
- package/packages/react/src/pages/index.ts +0 -2
- package/packages/react/tsconfig.json +0 -19
- package/packages/react/tsup.config.ts +0 -12
- package/packages/react-css/package.json +0 -36
- package/packages/react-css/src/components/ArticleJsonLd.tsx +0 -42
- package/packages/react-css/src/components/BreadcrumbsJsonLd.tsx +0 -24
- package/packages/react-css/src/components/MetaEditor.tsx +0 -147
- package/packages/react-css/src/components/SEOHead.tsx +0 -95
- package/packages/react-css/src/components/SEOPreview.tsx +0 -42
- package/packages/react-css/src/components/SEOScoreCard.tsx +0 -42
- package/packages/react-css/src/components/SitemapViewer.tsx +0 -36
- package/packages/react-css/src/components/index.ts +0 -7
- package/packages/react-css/src/index.ts +0 -9
- package/packages/react-css/src/pages/SEOAdminPage.tsx +0 -88
- package/packages/react-css/src/pages/SEOAnalyticsPage.tsx +0 -82
- package/packages/react-css/src/pages/index.ts +0 -2
- package/packages/react-css/tsup.config.ts +0 -2
- package/packages/shared/package.json +0 -42
- package/packages/shared/src/__tests__/seo.test.ts +0 -70
- package/packages/shared/src/config.ts +0 -297
- package/packages/shared/src/index.ts +0 -207
- package/packages/shared/tsconfig.json +0 -18
- package/packages/shared/tsup.config.ts +0 -11
- package/packages/shared/vitest.config.ts +0 -4
- package/packages/solidjs/package.json +0 -45
- package/packages/solidjs/src/components/ArticleJsonLd.tsx +0 -35
- package/packages/solidjs/src/components/BreadcrumbsJsonLd.tsx +0 -24
- package/packages/solidjs/src/components/MetaEditor.tsx +0 -155
- package/packages/solidjs/src/components/SEOHead.tsx +0 -109
- package/packages/solidjs/src/components/SEOPreview.tsx +0 -42
- package/packages/solidjs/src/components/SEOScoreCard.tsx +0 -57
- package/packages/solidjs/src/components/SitemapViewer.tsx +0 -44
- package/packages/solidjs/src/components/index.ts +0 -7
- package/packages/solidjs/src/index.ts +0 -11
- package/packages/solidjs/src/pages/SEOAdminPage.tsx +0 -104
- package/packages/solidjs/src/pages/SEOAnalyticsPage.tsx +0 -102
- package/packages/solidjs/src/pages/index.ts +0 -2
- package/packages/solidjs/src/primitives/index.ts +0 -4
- package/packages/solidjs/src/primitives/useSEO.ts +0 -27
- package/packages/solidjs/src/primitives/useSEOAdmin.ts +0 -42
- package/packages/solidjs/src/primitives/useSEOScore.ts +0 -7
- package/packages/solidjs/src/primitives/useSitemap.ts +0 -8
- package/packages/solidjs/tsconfig.json +0 -20
- package/packages/solidjs/tsup.config.ts +0 -12
- package/packages/solidjs-css/package.json +0 -35
- package/packages/solidjs-css/src/index.ts +0 -5
- package/packages/solidjs-css/src/primitives/index.ts +0 -1
- package/packages/solidjs-css/tsup.config.ts +0 -2
- package/pnpm-workspace.yaml +0 -2
- package/tsconfig.json +0 -23
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
var SEOConfigBuilder = class {
|
|
3
|
+
config = {
|
|
4
|
+
siteName: "",
|
|
5
|
+
siteUrl: ""
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Sets the site name
|
|
9
|
+
*/
|
|
10
|
+
withSiteName(name) {
|
|
11
|
+
this.config.siteName = name;
|
|
12
|
+
return this;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Sets the site URL
|
|
16
|
+
*/
|
|
17
|
+
withSiteUrl(url) {
|
|
18
|
+
this.config.siteUrl = url;
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Sets default OG image
|
|
23
|
+
*/
|
|
24
|
+
withDefaultImage(url) {
|
|
25
|
+
this.config.defaultImage = url;
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Sets Twitter handle
|
|
30
|
+
*/
|
|
31
|
+
withTwitterHandle(handle) {
|
|
32
|
+
this.config.twitterHandle = handle;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Sets title template (e.g., '%s — My Site')
|
|
37
|
+
*/
|
|
38
|
+
withTitleTemplate(template) {
|
|
39
|
+
this.config.titleTemplate = template;
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Sets default locale
|
|
44
|
+
*/
|
|
45
|
+
withLocale(locale) {
|
|
46
|
+
this.config.locale = locale;
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Sets Google Analytics ID
|
|
51
|
+
*/
|
|
52
|
+
withGoogleAnalyticsId(id) {
|
|
53
|
+
this.config.googleAnalyticsId = id;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Sets Google Tag Manager ID
|
|
58
|
+
*/
|
|
59
|
+
withGoogleTagManagerId(id) {
|
|
60
|
+
this.config.googleTagManagerId = id;
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Builds the configuration
|
|
65
|
+
*/
|
|
66
|
+
build() {
|
|
67
|
+
if (!this.config.siteName || !this.config.siteUrl) {
|
|
68
|
+
throw new Error("SEO config requires siteName and siteUrl");
|
|
69
|
+
}
|
|
70
|
+
return this.config;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
function createSEOConfig() {
|
|
74
|
+
return new SEOConfigBuilder();
|
|
75
|
+
}
|
|
76
|
+
function configureSEO(config) {
|
|
77
|
+
return {
|
|
78
|
+
siteName: config.siteName,
|
|
79
|
+
siteUrl: config.siteUrl,
|
|
80
|
+
titleTemplate: config.titleTemplate,
|
|
81
|
+
defaultImage: config.defaultImage,
|
|
82
|
+
twitterHandle: config.twitterHandle,
|
|
83
|
+
locale: config.locale ?? "en_US",
|
|
84
|
+
googleAnalyticsId: config.googleAnalyticsId,
|
|
85
|
+
googleTagManagerId: config.googleTagManagerId
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
var PageMetaBuilder = class {
|
|
89
|
+
constructor(config, title, description) {
|
|
90
|
+
this.config = config;
|
|
91
|
+
this.meta.title = title;
|
|
92
|
+
this.meta.description = description;
|
|
93
|
+
this.meta.og = {
|
|
94
|
+
title,
|
|
95
|
+
description,
|
|
96
|
+
type: "website",
|
|
97
|
+
url: config.siteUrl,
|
|
98
|
+
siteName: config.siteName,
|
|
99
|
+
image: config.defaultImage
|
|
100
|
+
};
|
|
101
|
+
this.meta.twitter = {
|
|
102
|
+
card: "summary_large_image",
|
|
103
|
+
title,
|
|
104
|
+
description,
|
|
105
|
+
image: config.defaultImage,
|
|
106
|
+
creator: config.twitterHandle
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
meta = {
|
|
110
|
+
og: {},
|
|
111
|
+
twitter: {}
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Adds keywords
|
|
115
|
+
*/
|
|
116
|
+
withKeywords(keywords) {
|
|
117
|
+
this.meta.keywords = keywords;
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Sets canonical URL
|
|
122
|
+
*/
|
|
123
|
+
withCanonical(url) {
|
|
124
|
+
this.meta.canonical = url;
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Sets OG image
|
|
129
|
+
*/
|
|
130
|
+
withImage(url, alt) {
|
|
131
|
+
if (this.meta.og) {
|
|
132
|
+
this.meta.og.image = url;
|
|
133
|
+
if (alt) this.meta.og.imageAlt = alt;
|
|
134
|
+
}
|
|
135
|
+
if (this.meta.twitter) {
|
|
136
|
+
this.meta.twitter.image = url;
|
|
137
|
+
}
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Sets OG type
|
|
142
|
+
*/
|
|
143
|
+
withType(type) {
|
|
144
|
+
if (this.meta.og) {
|
|
145
|
+
this.meta.og.type = type;
|
|
146
|
+
}
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Adds JSON-LD schema
|
|
151
|
+
*/
|
|
152
|
+
withJsonLd(schema) {
|
|
153
|
+
if (!this.meta.jsonLd) {
|
|
154
|
+
this.meta.jsonLd = [];
|
|
155
|
+
}
|
|
156
|
+
this.meta.jsonLd.push(schema);
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Sets robots meta
|
|
161
|
+
*/
|
|
162
|
+
withRobots(robots) {
|
|
163
|
+
this.meta.robots = robots;
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Adds alternate language links
|
|
168
|
+
*/
|
|
169
|
+
withAlternates(alternates) {
|
|
170
|
+
this.meta.alternates = alternates;
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Builds the meta tags
|
|
175
|
+
*/
|
|
176
|
+
build() {
|
|
177
|
+
return {
|
|
178
|
+
title: this.meta.title || "",
|
|
179
|
+
description: this.meta.description || "",
|
|
180
|
+
keywords: this.meta.keywords || [],
|
|
181
|
+
canonical: this.meta.canonical,
|
|
182
|
+
og: this.meta.og || { type: "website", title: "", description: "", url: "" },
|
|
183
|
+
twitter: this.meta.twitter || { card: "summary", title: "", description: "" },
|
|
184
|
+
robots: this.meta.robots,
|
|
185
|
+
alternates: this.meta.alternates,
|
|
186
|
+
jsonLd: this.meta.jsonLd
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
function createPageMeta(config, title, description) {
|
|
191
|
+
return new PageMetaBuilder(config, title, description);
|
|
192
|
+
}
|
|
193
|
+
function validateSEOMeta(meta) {
|
|
194
|
+
const errors = [];
|
|
195
|
+
if (!meta.title || meta.title.length === 0) {
|
|
196
|
+
errors.push("Title is required");
|
|
197
|
+
}
|
|
198
|
+
if (meta.title && meta.title.length > 60) {
|
|
199
|
+
errors.push("Title should be under 60 characters");
|
|
200
|
+
}
|
|
201
|
+
if (!meta.description || meta.description.length === 0) {
|
|
202
|
+
errors.push("Description is required");
|
|
203
|
+
}
|
|
204
|
+
if (meta.description && meta.description.length > 160) {
|
|
205
|
+
errors.push("Description should be under 160 characters");
|
|
206
|
+
}
|
|
207
|
+
if (!meta.og?.image && !meta.twitter?.image) {
|
|
208
|
+
errors.push("At least one image (OG or Twitter) is recommended");
|
|
209
|
+
}
|
|
210
|
+
if (!meta.keywords || meta.keywords.length === 0) {
|
|
211
|
+
errors.push("At least one keyword is recommended");
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
valid: errors.length === 0,
|
|
215
|
+
errors
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
var seoPresets = {
|
|
219
|
+
/**
|
|
220
|
+
* Blog article preset
|
|
221
|
+
*/
|
|
222
|
+
article: (_config) => ({
|
|
223
|
+
og: { type: "article", title: "", description: "", url: "" },
|
|
224
|
+
robots: "index, follow"
|
|
225
|
+
}),
|
|
226
|
+
/**
|
|
227
|
+
* E-commerce product preset
|
|
228
|
+
*/
|
|
229
|
+
product: (_config) => ({
|
|
230
|
+
og: { type: "product", title: "", description: "", url: "" },
|
|
231
|
+
robots: "index, follow"
|
|
232
|
+
}),
|
|
233
|
+
/**
|
|
234
|
+
* Service/company page preset
|
|
235
|
+
*/
|
|
236
|
+
business: (_config) => ({
|
|
237
|
+
robots: "index, follow"
|
|
238
|
+
}),
|
|
239
|
+
/**
|
|
240
|
+
* Private/unlisted page preset
|
|
241
|
+
*/
|
|
242
|
+
private: () => ({
|
|
243
|
+
robots: "noindex, nofollow"
|
|
244
|
+
})
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// src/index.ts
|
|
248
|
+
function buildTitle(pageTitle, siteName, sep = " | ") {
|
|
249
|
+
return `${pageTitle}${sep}${siteName}`;
|
|
250
|
+
}
|
|
251
|
+
function applyTitleTemplate(pageTitle, template) {
|
|
252
|
+
if (!template) return pageTitle;
|
|
253
|
+
return template.replace("%s", pageTitle);
|
|
254
|
+
}
|
|
255
|
+
function truncateDescription(desc, max = 160) {
|
|
256
|
+
if (desc.length <= max) return desc;
|
|
257
|
+
return desc.slice(0, max - 3) + "...";
|
|
258
|
+
}
|
|
259
|
+
function articleSchema(data) {
|
|
260
|
+
return {
|
|
261
|
+
"@context": "https://schema.org",
|
|
262
|
+
"@type": "Article",
|
|
263
|
+
...data
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function productSchema(data) {
|
|
267
|
+
return {
|
|
268
|
+
"@context": "https://schema.org",
|
|
269
|
+
"@type": "Product",
|
|
270
|
+
name: data.name,
|
|
271
|
+
description: data.description,
|
|
272
|
+
url: data.url,
|
|
273
|
+
image: data.image,
|
|
274
|
+
offers: {
|
|
275
|
+
"@type": "Offer",
|
|
276
|
+
price: data.price,
|
|
277
|
+
priceCurrency: data.currency
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function orgSchema(data) {
|
|
282
|
+
return {
|
|
283
|
+
"@context": "https://schema.org",
|
|
284
|
+
"@type": "Organization",
|
|
285
|
+
...data
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function breadcrumbSchema(items) {
|
|
289
|
+
return {
|
|
290
|
+
"@context": "https://schema.org",
|
|
291
|
+
"@type": "BreadcrumbList",
|
|
292
|
+
itemListElement: items.map((item, i) => ({
|
|
293
|
+
"@type": "ListItem",
|
|
294
|
+
position: i + 1,
|
|
295
|
+
name: item.name,
|
|
296
|
+
item: item.url
|
|
297
|
+
}))
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function faqSchema(items) {
|
|
301
|
+
return {
|
|
302
|
+
"@context": "https://schema.org",
|
|
303
|
+
"@type": "FAQPage",
|
|
304
|
+
mainEntity: items.map((q) => ({
|
|
305
|
+
"@type": "Question",
|
|
306
|
+
name: q.question,
|
|
307
|
+
acceptedAnswer: {
|
|
308
|
+
"@type": "Answer",
|
|
309
|
+
text: q.answer
|
|
310
|
+
}
|
|
311
|
+
}))
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function webApplicationSchema(data) {
|
|
315
|
+
const schema = {
|
|
316
|
+
"@context": "https://schema.org",
|
|
317
|
+
"@type": "WebApplication",
|
|
318
|
+
name: data.name,
|
|
319
|
+
url: data.url,
|
|
320
|
+
description: data.description
|
|
321
|
+
};
|
|
322
|
+
if (data.applicationCategory) schema.applicationCategory = data.applicationCategory;
|
|
323
|
+
if (data.operatingSystem) schema.operatingSystem = data.operatingSystem;
|
|
324
|
+
if (data.offers) {
|
|
325
|
+
schema.offers = {
|
|
326
|
+
"@type": "Offer",
|
|
327
|
+
price: data.offers.price,
|
|
328
|
+
priceCurrency: data.offers.currency
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
return schema;
|
|
332
|
+
}
|
|
333
|
+
function buildCanonical(path, baseUrl) {
|
|
334
|
+
return `${baseUrl.replace(/\/$/, "")}/${path.replace(/^\//, "")}`;
|
|
335
|
+
}
|
|
336
|
+
function buildAlternates(locales, baseUrl, path) {
|
|
337
|
+
return Object.fromEntries(locales.map((l) => [l, `${baseUrl}/${l}${path}`]));
|
|
338
|
+
}
|
|
339
|
+
function generateRobotsTxt(config) {
|
|
340
|
+
const lines = ["User-agent: *"];
|
|
341
|
+
config.allow.forEach((p) => lines.push(`Allow: ${p}`));
|
|
342
|
+
config.disallow.forEach((p) => lines.push(`Disallow: ${p}`));
|
|
343
|
+
if (config.sitemap) lines.push(`Sitemap: ${config.sitemap}`);
|
|
344
|
+
return lines.join("\n");
|
|
345
|
+
}
|
|
346
|
+
function generateSitemapXml(entries) {
|
|
347
|
+
const urls = entries.map(
|
|
348
|
+
(e) => ` <url>
|
|
349
|
+
<loc>${e.url}</loc>${e.lastmod ? `
|
|
350
|
+
<lastmod>${e.lastmod}</lastmod>` : ""}${e.changefreq ? `
|
|
351
|
+
<changefreq>${e.changefreq}</changefreq>` : ""}${e.priority !== void 0 ? `
|
|
352
|
+
<priority>${e.priority}</priority>` : ""}
|
|
353
|
+
</url>`
|
|
354
|
+
).join("\n");
|
|
355
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
356
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
357
|
+
${urls}
|
|
358
|
+
</urlset>`;
|
|
359
|
+
}
|
|
360
|
+
function estimateReadingTime(content) {
|
|
361
|
+
return Math.ceil(content.split(/\s+/).length / 200);
|
|
362
|
+
}
|
|
363
|
+
function calcSEOScore(meta) {
|
|
364
|
+
const issues = [];
|
|
365
|
+
if (!meta.title || meta.title.length < 10) issues.push("Title too short (< 10 chars)");
|
|
366
|
+
if (meta.title && meta.title.length > 60) issues.push("Title too long (> 60 chars)");
|
|
367
|
+
if (!meta.description || meta.description.length < 50)
|
|
368
|
+
issues.push("Description too short (< 50 chars)");
|
|
369
|
+
if (meta.description && meta.description.length > 160)
|
|
370
|
+
issues.push("Description too long (> 160 chars)");
|
|
371
|
+
if (!meta.keywords || meta.keywords.length === 0) issues.push("No keywords specified");
|
|
372
|
+
if (!meta.canonical) issues.push("No canonical URL");
|
|
373
|
+
if (!meta.og.image) issues.push("No OG image");
|
|
374
|
+
if (!meta.twitter.image) issues.push("No Twitter image");
|
|
375
|
+
const score = Math.max(0, 100 - issues.length * 12);
|
|
376
|
+
return { score, issues };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export { PageMetaBuilder, SEOConfigBuilder, applyTitleTemplate, articleSchema, breadcrumbSchema, buildAlternates, buildCanonical, buildTitle, calcSEOScore, configureSEO, createPageMeta, createSEOConfig, estimateReadingTime, faqSchema, generateRobotsTxt, generateSitemapXml, orgSchema, productSchema, seoPresets, truncateDescription, validateSEOMeta, webApplicationSchema };
|
|
380
|
+
//# sourceMappingURL=index.js.map
|
|
381
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * SEO configuration and utilities\n */\n\nimport type { SEOConfig, SEOMeta, OGMeta, TwitterMeta } from './index'\n\n/**\n * SEO configuration builder for type-safe setup\n *\n * @example\n * ```ts\n * const seoConfig = createSEOConfig()\n * .withSiteName('My Site')\n * .withSiteUrl('https://example.com')\n * .withDefaultImage('https://example.com/og-image.jpg')\n * .withTwitterHandle('@mysite')\n * ```\n */\nexport class SEOConfigBuilder {\n private config: SEOConfig = {\n siteName: '',\n siteUrl: '',\n }\n\n /**\n * Sets the site name\n */\n withSiteName(name: string): this {\n this.config.siteName = name\n return this\n }\n\n /**\n * Sets the site URL\n */\n withSiteUrl(url: string): this {\n this.config.siteUrl = url\n return this\n }\n\n /**\n * Sets default OG image\n */\n withDefaultImage(url: string): this {\n this.config.defaultImage = url\n return this\n }\n\n /**\n * Sets Twitter handle\n */\n withTwitterHandle(handle: string): this {\n this.config.twitterHandle = handle\n return this\n }\n\n /**\n * Sets title template (e.g., '%s — My Site')\n */\n withTitleTemplate(template: string): this {\n this.config.titleTemplate = template\n return this\n }\n\n /**\n * Sets default locale\n */\n withLocale(locale: string): this {\n this.config.locale = locale\n return this\n }\n\n /**\n * Sets Google Analytics ID\n */\n withGoogleAnalyticsId(id: string): this {\n this.config.googleAnalyticsId = id\n return this\n }\n\n /**\n * Sets Google Tag Manager ID\n */\n withGoogleTagManagerId(id: string): this {\n this.config.googleTagManagerId = id\n return this\n }\n\n /**\n * Builds the configuration\n */\n build(): SEOConfig {\n if (!this.config.siteName || !this.config.siteUrl) {\n throw new Error('SEO config requires siteName and siteUrl')\n }\n return this.config\n }\n}\n\n/**\n * Creates a new SEO configuration builder\n */\nexport function createSEOConfig(): SEOConfigBuilder {\n return new SEOConfigBuilder()\n}\n\n/**\n * Creates a validated SEO configuration from a partial config object.\n * Requires siteName and siteUrl at minimum.\n *\n * @example\n * ```ts\n * const config = configureSEO({\n * siteName: 'My Site',\n * siteUrl: 'https://example.com',\n * titleTemplate: '%s — My Site',\n * })\n * ```\n */\nexport function configureSEO(config: Partial<SEOConfig> & { siteName: string; siteUrl: string }): SEOConfig {\n return {\n siteName: config.siteName,\n siteUrl: config.siteUrl,\n titleTemplate: config.titleTemplate,\n defaultImage: config.defaultImage,\n twitterHandle: config.twitterHandle,\n locale: config.locale ?? 'en_US',\n googleAnalyticsId: config.googleAnalyticsId,\n googleTagManagerId: config.googleTagManagerId,\n }\n}\n\n/**\n * Meta tags builder for individual pages\n *\n * @example\n * ```ts\n * const meta = createPageMeta({\n * title: 'Home',\n * description: 'Welcome to my site'\n * })\n * .withKeywords(['web', 'development'])\n * .withImage('https://example.com/image.jpg')\n * ```\n */\nexport class PageMetaBuilder {\n private meta: Partial<SEOMeta> = {\n og: {} as OGMeta,\n twitter: {} as TwitterMeta,\n }\n\n constructor(private config: SEOConfig, title: string, description: string) {\n this.meta.title = title\n this.meta.description = description\n this.meta.og = {\n title,\n description,\n type: 'website',\n url: config.siteUrl,\n siteName: config.siteName,\n image: config.defaultImage,\n }\n this.meta.twitter = {\n card: 'summary_large_image',\n title,\n description,\n image: config.defaultImage,\n creator: config.twitterHandle,\n }\n }\n\n /**\n * Adds keywords\n */\n withKeywords(keywords: string[]): this {\n this.meta.keywords = keywords\n return this\n }\n\n /**\n * Sets canonical URL\n */\n withCanonical(url: string): this {\n this.meta.canonical = url\n return this\n }\n\n /**\n * Sets OG image\n */\n withImage(url: string, alt?: string): this {\n if (this.meta.og) {\n this.meta.og.image = url\n if (alt) this.meta.og.imageAlt = alt\n }\n if (this.meta.twitter) {\n this.meta.twitter.image = url\n }\n return this\n }\n\n /**\n * Sets OG type\n */\n withType(type: 'website' | 'article' | 'product'): this {\n if (this.meta.og) {\n this.meta.og.type = type\n }\n return this\n }\n\n /**\n * Adds JSON-LD schema\n */\n withJsonLd(schema: Record<string, unknown>): this {\n if (!this.meta.jsonLd) {\n this.meta.jsonLd = []\n }\n this.meta.jsonLd.push(schema)\n return this\n }\n\n /**\n * Sets robots meta\n */\n withRobots(robots: string): this {\n this.meta.robots = robots\n return this\n }\n\n /**\n * Adds alternate language links\n */\n withAlternates(alternates: Record<string, string>): this {\n this.meta.alternates = alternates\n return this\n }\n\n /**\n * Builds the meta tags\n */\n build(): SEOMeta {\n return {\n title: this.meta.title || '',\n description: this.meta.description || '',\n keywords: this.meta.keywords || [],\n canonical: this.meta.canonical,\n og: this.meta.og || ({ type: 'website', title: '', description: '', url: '' } as OGMeta),\n twitter: this.meta.twitter || ({ card: 'summary', title: '', description: '' } as TwitterMeta),\n robots: this.meta.robots,\n alternates: this.meta.alternates,\n jsonLd: this.meta.jsonLd,\n }\n }\n}\n\n/**\n * Creates page-specific meta tags\n */\nexport function createPageMeta(config: SEOConfig, title: string, description: string): PageMetaBuilder {\n return new PageMetaBuilder(config, title, description)\n}\n\n/**\n * Validates SEO meta configuration\n */\nexport function validateSEOMeta(meta: SEOMeta): { valid: boolean; errors: string[] } {\n const errors: string[] = []\n\n if (!meta.title || meta.title.length === 0) {\n errors.push('Title is required')\n }\n if (meta.title && meta.title.length > 60) {\n errors.push('Title should be under 60 characters')\n }\n\n if (!meta.description || meta.description.length === 0) {\n errors.push('Description is required')\n }\n if (meta.description && meta.description.length > 160) {\n errors.push('Description should be under 160 characters')\n }\n\n if (!meta.og?.image && !meta.twitter?.image) {\n errors.push('At least one image (OG or Twitter) is recommended')\n }\n\n if (!meta.keywords || meta.keywords.length === 0) {\n errors.push('At least one keyword is recommended')\n }\n\n return {\n valid: errors.length === 0,\n errors,\n }\n}\n\n/**\n * Presets for common SEO configurations\n */\nexport const seoPresets = {\n /**\n * Blog article preset\n */\n article: (_config: SEOConfig): Partial<SEOMeta> => ({\n og: { type: 'article', title: '', description: '', url: '' },\n robots: 'index, follow',\n }),\n\n /**\n * E-commerce product preset\n */\n product: (_config: SEOConfig): Partial<SEOMeta> => ({\n og: { type: 'product', title: '', description: '', url: '' },\n robots: 'index, follow',\n }),\n\n /**\n * Service/company page preset\n */\n business: (_config: SEOConfig): Partial<SEOMeta> => ({\n robots: 'index, follow',\n }),\n\n /**\n * Private/unlisted page preset\n */\n private: (): Partial<SEOMeta> => ({\n robots: 'noindex, nofollow',\n }),\n}\n","// ─── Config Builders & Utilities ──────────────────────────────────\nexport {\n SEOConfigBuilder,\n createSEOConfig,\n configureSEO,\n PageMetaBuilder,\n createPageMeta,\n validateSEOMeta,\n seoPresets,\n} from './config'\n\n// ─── SEO Metadata Interfaces ─────────────────────────────────────\n\nexport interface OGMeta {\n title: string\n description: string\n image?: string\n imageAlt?: string\n type: 'website' | 'article' | 'product'\n url: string\n siteName?: string\n}\n\nexport interface TwitterMeta {\n card: 'summary' | 'summary_large_image'\n title: string\n description: string\n image?: string\n creator?: string\n}\n\nexport interface SEOMeta {\n title: string\n description: string\n keywords: string[]\n canonical?: string\n og: OGMeta\n twitter: TwitterMeta\n robots?: string\n alternates?: Record<string, string>\n jsonLd?: Record<string, unknown>[]\n}\n\nexport interface SitemapEntry {\n url: string\n lastmod?: string\n changefreq?: string\n priority?: number\n}\n\nexport interface RobotsTxt {\n allow: string[]\n disallow: string[]\n sitemap?: string\n}\n\nexport interface SEOConfig {\n siteName: string\n siteUrl: string\n titleTemplate?: string\n defaultImage?: string\n twitterHandle?: string\n locale?: string\n googleAnalyticsId?: string\n googleTagManagerId?: string\n}\n\n// ─── Utility Functions ──────────────────────────────────────────\n\nexport function buildTitle(pageTitle: string, siteName: string, sep = ' | '): string {\n return `${pageTitle}${sep}${siteName}`\n}\n\nexport function applyTitleTemplate(pageTitle: string, template?: string): string {\n if (!template) return pageTitle\n return template.replace('%s', pageTitle)\n}\n\nexport function truncateDescription(desc: string, max = 160): string {\n if (desc.length <= max) return desc\n return desc.slice(0, max - 3) + '...'\n}\n\n// ─── JSON-LD Schema Builders ────────────────────────────────────\n\nexport function articleSchema(data: {\n title: string\n description: string\n author: string\n datePublished: string\n url: string\n image?: string\n}): Record<string, unknown> {\n return {\n '@context': 'https://schema.org',\n '@type': 'Article',\n ...data,\n }\n}\n\nexport function productSchema(data: {\n name: string\n description: string\n price: number\n currency: string\n url: string\n image?: string\n}): Record<string, unknown> {\n return {\n '@context': 'https://schema.org',\n '@type': 'Product',\n name: data.name,\n description: data.description,\n url: data.url,\n image: data.image,\n offers: {\n '@type': 'Offer',\n price: data.price,\n priceCurrency: data.currency,\n },\n }\n}\n\nexport function orgSchema(data: {\n name: string\n url: string\n logo?: string\n sameAs?: string[]\n}): Record<string, unknown> {\n return {\n '@context': 'https://schema.org',\n '@type': 'Organization',\n ...data,\n }\n}\n\nexport function breadcrumbSchema(items: {\n name: string\n url: string\n}[]): Record<string, unknown> {\n return {\n '@context': 'https://schema.org',\n '@type': 'BreadcrumbList',\n itemListElement: items.map((item, i) => ({\n '@type': 'ListItem',\n position: i + 1,\n name: item.name,\n item: item.url,\n })),\n }\n}\n\nexport function faqSchema(items: {\n question: string\n answer: string\n}[]): Record<string, unknown> {\n return {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: items.map((q) => ({\n '@type': 'Question',\n name: q.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: q.answer,\n },\n })),\n }\n}\n\nexport function webApplicationSchema(data: {\n name: string\n url: string\n description: string\n applicationCategory?: string\n operatingSystem?: string\n offers?: { price: number; currency: string }\n}): Record<string, unknown> {\n const schema: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': 'WebApplication',\n name: data.name,\n url: data.url,\n description: data.description,\n }\n if (data.applicationCategory) schema.applicationCategory = data.applicationCategory\n if (data.operatingSystem) schema.operatingSystem = data.operatingSystem\n if (data.offers) {\n schema.offers = {\n '@type': 'Offer',\n price: data.offers.price,\n priceCurrency: data.offers.currency,\n }\n }\n return schema\n}\n\nexport function buildCanonical(path: string, baseUrl: string): string {\n return `${baseUrl.replace(/\\/$/, '')}/${path.replace(/^\\//, '')}`\n}\n\nexport function buildAlternates(\n locales: string[],\n baseUrl: string,\n path: string,\n): Record<string, string> {\n return Object.fromEntries(locales.map((l) => [l, `${baseUrl}/${l}${path}`]))\n}\n\nexport function generateRobotsTxt(config: RobotsTxt): string {\n const lines = ['User-agent: *']\n config.allow.forEach((p) => lines.push(`Allow: ${p}`))\n config.disallow.forEach((p) => lines.push(`Disallow: ${p}`))\n if (config.sitemap) lines.push(`Sitemap: ${config.sitemap}`)\n return lines.join('\\n')\n}\n\nexport function generateSitemapXml(entries: SitemapEntry[]): string {\n const urls = entries\n .map(\n (e) =>\n ` <url>\\n <loc>${e.url}</loc>${e.lastmod ? `\\n <lastmod>${e.lastmod}</lastmod>` : ''}${e.changefreq ? `\\n <changefreq>${e.changefreq}</changefreq>` : ''}${e.priority !== undefined ? `\\n <priority>${e.priority}</priority>` : ''}\\n </url>`,\n )\n .join('\\n')\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${urls}\\n</urlset>`\n}\n\nexport function estimateReadingTime(content: string): number {\n return Math.ceil(content.split(/\\s+/).length / 200)\n}\n\nexport function calcSEOScore(meta: SEOMeta): {\n score: number\n issues: string[]\n} {\n const issues: string[] = []\n\n if (!meta.title || meta.title.length < 10) issues.push('Title too short (< 10 chars)')\n if (meta.title && meta.title.length > 60) issues.push('Title too long (> 60 chars)')\n if (!meta.description || meta.description.length < 50)\n issues.push('Description too short (< 50 chars)')\n if (meta.description && meta.description.length > 160)\n issues.push('Description too long (> 160 chars)')\n if (!meta.keywords || meta.keywords.length === 0) issues.push('No keywords specified')\n if (!meta.canonical) issues.push('No canonical URL')\n if (!meta.og.image) issues.push('No OG image')\n if (!meta.twitter.image) issues.push('No Twitter image')\n\n const score = Math.max(0, 100 - issues.length * 12)\n return { score, issues }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
# ✦ @geenius-
|
|
1
|
+
# ✦ @geenius/seo-solidjs\n\n> Geenius Seo — SolidJS components & primitives\n\n---\n\n## Overview\nBuilt with Steve Jobs-level minimalism and Jony Ive-level craftsmanship, this package is designed to deliver unparalleled developer experience (DX) and rock-solid performance.\n\n## Installation\n\n```bash\npnpm add @geenius/seo-solidjs\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius/seo-solidjs';\n\n// Initialize the module with absolute precision\ninit({\n mode: 'premium',\n});\n```\n\n## Architecture\n- **Zero-config**: It just works.\n- **Strictly Typed**: Fully written in TypeScript for flawless IntelliSense.\n- **Framework Agnostic**: seamlessly integrates into the Geenius ecosystem.\n\n---\n\n*Designed by Antigravity HQ*\n
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { SEOConfig, SEOMeta, SitemapEntry } from '@geenius/seo-shared';
|
|
2
|
+
export * from '@geenius/seo-shared';
|
|
3
|
+
import * as solid_js from 'solid-js';
|
|
4
|
+
import { JSX, Accessor } from 'solid-js';
|
|
5
|
+
|
|
6
|
+
interface SeoContextValue {
|
|
7
|
+
config: SEOConfig;
|
|
8
|
+
}
|
|
9
|
+
interface SeoProviderProps {
|
|
10
|
+
config: SEOConfig;
|
|
11
|
+
children: JSX.Element;
|
|
12
|
+
}
|
|
13
|
+
declare function SeoProvider(props: SeoProviderProps): JSX.Element;
|
|
14
|
+
declare function useSEOContext(): SeoContextValue;
|
|
15
|
+
|
|
16
|
+
interface CreateSEOOptions {
|
|
17
|
+
path?: string;
|
|
18
|
+
}
|
|
19
|
+
declare function createSEO(options?: CreateSEOOptions): {
|
|
20
|
+
meta: solid_js.Accessor<SEOMeta | null>;
|
|
21
|
+
updateMeta: (newMeta: SEOMeta) => void;
|
|
22
|
+
isLoading: solid_js.Accessor<boolean>;
|
|
23
|
+
};
|
|
24
|
+
/** @deprecated Use `createSEO` instead */
|
|
25
|
+
declare const useSEO: typeof createSEO;
|
|
26
|
+
|
|
27
|
+
interface PageEntry {
|
|
28
|
+
path: string;
|
|
29
|
+
meta: SEOMeta;
|
|
30
|
+
}
|
|
31
|
+
interface CreateSEOAdminOptions {
|
|
32
|
+
/** Pre-loaded pages (e.g. from Convex createQuery) */
|
|
33
|
+
initialPages?: Accessor<PageEntry[] | undefined>;
|
|
34
|
+
/** Callback when a page is upserted */
|
|
35
|
+
onUpsert?: (path: string, meta: SEOMeta) => Promise<void>;
|
|
36
|
+
/** Callback when a page is deleted */
|
|
37
|
+
onDelete?: (path: string) => Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Primitive to manage multiple page SEO metadata entries.
|
|
41
|
+
* Supports optional Convex backend via callback props.
|
|
42
|
+
*/
|
|
43
|
+
declare function createSEOAdmin(options?: CreateSEOAdminOptions): {
|
|
44
|
+
pages: Accessor<PageEntry[]>;
|
|
45
|
+
upsertMeta: (path: string, meta: SEOMeta) => Promise<void>;
|
|
46
|
+
deleteMeta: (path: string) => Promise<void>;
|
|
47
|
+
isLoading: Accessor<boolean>;
|
|
48
|
+
};
|
|
49
|
+
/** @deprecated Use `createSEOAdmin` instead */
|
|
50
|
+
declare const useSEOAdmin: typeof createSEOAdmin;
|
|
51
|
+
|
|
52
|
+
declare function createSEOScore(meta: () => SEOMeta): solid_js.Accessor<{
|
|
53
|
+
score: number;
|
|
54
|
+
issues: string[];
|
|
55
|
+
}>;
|
|
56
|
+
/** @deprecated Use `createSEOScore` instead */
|
|
57
|
+
declare const useSEOScore: typeof createSEOScore;
|
|
58
|
+
|
|
59
|
+
declare function createSitemap(entries: () => SitemapEntry[]): {
|
|
60
|
+
xml: solid_js.Accessor<string>;
|
|
61
|
+
};
|
|
62
|
+
/** @deprecated Use `createSitemap` instead */
|
|
63
|
+
declare const useSitemap: typeof createSitemap;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns a canonical URL for the given path using the site URL from SeoProvider.
|
|
67
|
+
*/
|
|
68
|
+
declare function createCanonical(path?: Accessor<string | undefined>): Accessor<string>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Injects JSON-LD structured data into the document head.
|
|
72
|
+
* Cleans up on disposal or when data changes.
|
|
73
|
+
*/
|
|
74
|
+
declare function createStructuredData(data: Accessor<Record<string, unknown> | Record<string, unknown>[] | undefined>): void;
|
|
75
|
+
|
|
76
|
+
interface SEOHeadProps {
|
|
77
|
+
meta: SEOMeta;
|
|
78
|
+
}
|
|
79
|
+
declare function SEOHead(props: SEOHeadProps): null;
|
|
80
|
+
|
|
81
|
+
interface SEOPreviewProps {
|
|
82
|
+
meta: SEOMeta;
|
|
83
|
+
}
|
|
84
|
+
declare function SEOPreview(props: SEOPreviewProps): solid_js.JSX.Element;
|
|
85
|
+
|
|
86
|
+
interface SEOScoreCardProps {
|
|
87
|
+
meta: SEOMeta;
|
|
88
|
+
}
|
|
89
|
+
declare function SEOScoreCard(props: SEOScoreCardProps): solid_js.JSX.Element;
|
|
90
|
+
|
|
91
|
+
interface MetaEditorProps {
|
|
92
|
+
meta: SEOMeta;
|
|
93
|
+
onChange: (meta: SEOMeta) => void;
|
|
94
|
+
}
|
|
95
|
+
declare function MetaEditor(props: MetaEditorProps): solid_js.JSX.Element;
|
|
96
|
+
|
|
97
|
+
interface SitemapViewerProps {
|
|
98
|
+
entries: SitemapEntry[];
|
|
99
|
+
}
|
|
100
|
+
declare function SitemapViewer(props: SitemapViewerProps): solid_js.JSX.Element;
|
|
101
|
+
|
|
102
|
+
interface BreadcrumbsJsonLdProps {
|
|
103
|
+
items: {
|
|
104
|
+
name: string;
|
|
105
|
+
url: string;
|
|
106
|
+
}[];
|
|
107
|
+
}
|
|
108
|
+
declare function BreadcrumbsJsonLd(props: BreadcrumbsJsonLdProps): null;
|
|
109
|
+
|
|
110
|
+
interface ArticleJsonLdProps {
|
|
111
|
+
title: string;
|
|
112
|
+
description: string;
|
|
113
|
+
author: string;
|
|
114
|
+
datePublished: string;
|
|
115
|
+
url: string;
|
|
116
|
+
image?: string;
|
|
117
|
+
}
|
|
118
|
+
declare function ArticleJsonLd(props: ArticleJsonLdProps): null;
|
|
119
|
+
|
|
120
|
+
interface JsonLdProps {
|
|
121
|
+
type: string;
|
|
122
|
+
data: Record<string, unknown>;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Renders a JSON-LD structured data script in the document head.
|
|
126
|
+
*/
|
|
127
|
+
declare function JsonLd(props: JsonLdProps): null;
|
|
128
|
+
|
|
129
|
+
declare function SEOAdminPage(): solid_js.JSX.Element;
|
|
130
|
+
|
|
131
|
+
declare function SEOAnalyticsPage(): solid_js.JSX.Element;
|
|
132
|
+
|
|
133
|
+
export { ArticleJsonLd, BreadcrumbsJsonLd, type CreateSEOAdminOptions, JsonLd, type JsonLdProps, MetaEditor, SEOAdminPage, SEOAnalyticsPage, SEOHead, SEOPreview, SEOScoreCard, SeoProvider, type SeoProviderProps, SitemapViewer, createCanonical, createSEO, createSEOAdmin, createSEOScore, createSitemap, createStructuredData, useSEO, useSEOAdmin, useSEOContext, useSEOScore, useSitemap };
|