sanity-plugin-seofields 1.8.0 → 1.9.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.
Files changed (113) hide show
  1. package/README.md +437 -26
  2. package/dist/SeoHealthTool-2COI27KI.cjs +8 -0
  3. package/dist/SeoHealthTool-2COI27KI.cjs.map +1 -0
  4. package/dist/SeoHealthTool-CWI3KB2V.js +8 -0
  5. package/dist/SeoHealthTool-CWI3KB2V.js.map +1 -0
  6. package/dist/{SeoPreview-3EXR6FD4.cjs → SeoPreview-LMZAWWOE.cjs} +35 -38
  7. package/dist/SeoPreview-LMZAWWOE.cjs.map +1 -0
  8. package/dist/{SeoPreview-4PPAF4NO.js → SeoPreview-UNQBKTQO.js} +14 -11
  9. package/dist/SeoPreview-UNQBKTQO.js.map +1 -0
  10. package/dist/chunk-3OK3S2F7.cjs +673 -0
  11. package/dist/chunk-3OK3S2F7.cjs.map +1 -0
  12. package/dist/{chunk-B5IVI5LP.cjs → chunk-5XTQRILL.cjs} +286 -236
  13. package/dist/chunk-5XTQRILL.cjs.map +1 -0
  14. package/dist/{chunk-254YHUN3.cjs → chunk-7N4MLTMR.js} +8 -6
  15. package/dist/chunk-7N4MLTMR.js.map +1 -0
  16. package/dist/{chunk-DDAAVRWG.js → chunk-A57XNQC7.cjs} +9 -4
  17. package/dist/chunk-A57XNQC7.cjs.map +1 -0
  18. package/dist/chunk-BWLJDK5J.js +624 -0
  19. package/dist/chunk-BWLJDK5J.js.map +1 -0
  20. package/dist/chunk-FUUWBQE2.cjs +195 -0
  21. package/dist/chunk-FUUWBQE2.cjs.map +1 -0
  22. package/dist/{chunk-ZBHLMQTS.cjs → chunk-HHO2AKAP.js} +44 -17
  23. package/dist/chunk-HHO2AKAP.js.map +1 -0
  24. package/dist/chunk-KIEO6QG3.js +195 -0
  25. package/dist/chunk-KIEO6QG3.js.map +1 -0
  26. package/dist/chunk-R2U7JF7U.cjs +624 -0
  27. package/dist/chunk-R2U7JF7U.cjs.map +1 -0
  28. package/dist/{chunk-VR44E62I.js → chunk-XZKQWV2H.js} +204 -33
  29. package/dist/chunk-XZKQWV2H.js.map +1 -0
  30. package/dist/{chunk-HDZZQCH7.js → chunk-Y46ACXM4.cjs} +45 -5
  31. package/dist/chunk-Y46ACXM4.cjs.map +1 -0
  32. package/dist/chunk-Z5AOUU4H.js +673 -0
  33. package/dist/chunk-Z5AOUU4H.js.map +1 -0
  34. package/dist/cli.js +27 -27
  35. package/dist/{component-DEXwtemT.d.ts → component-CjT1hvxh.d.ts} +2 -2
  36. package/dist/{component-BzI-LHPw.d.cts → component-WYh0z8as.d.cts} +2 -2
  37. package/dist/define-cli.cjs +2 -4
  38. package/dist/define-cli.cjs.map +1 -1
  39. package/dist/define-cli.js +4 -4
  40. package/dist/define-cli.js.map +1 -1
  41. package/dist/head.cjs +14 -0
  42. package/dist/head.cjs.map +1 -0
  43. package/dist/head.d.cts +254 -0
  44. package/dist/head.d.ts +254 -0
  45. package/dist/head.js +14 -0
  46. package/dist/head.js.map +1 -0
  47. package/dist/index.cjs +978 -367
  48. package/dist/index.cjs.map +1 -1
  49. package/dist/index.d.cts +6 -374
  50. package/dist/index.d.ts +6 -374
  51. package/dist/index.js +920 -286
  52. package/dist/index.js.map +1 -1
  53. package/dist/next.cjs +204 -455
  54. package/dist/next.cjs.map +1 -1
  55. package/dist/next.d.cts +8 -206
  56. package/dist/next.d.ts +8 -206
  57. package/dist/next.js +181 -111
  58. package/dist/next.js.map +1 -1
  59. package/dist/plugin-DQdThyxG.d.ts +449 -0
  60. package/dist/plugin-DoGeZP79.d.cts +449 -0
  61. package/dist/schema/next.cjs +173 -327
  62. package/dist/schema/next.cjs.map +1 -1
  63. package/dist/schema/next.d.cts +4 -4
  64. package/dist/schema/next.d.ts +4 -4
  65. package/dist/schema/next.js +172 -8
  66. package/dist/schema/next.js.map +1 -1
  67. package/dist/schema.cjs +376 -517
  68. package/dist/schema.cjs.map +1 -1
  69. package/dist/schema.d.cts +5 -5
  70. package/dist/schema.d.ts +5 -5
  71. package/dist/schema.js +216 -11
  72. package/dist/schema.js.map +1 -1
  73. package/dist/server.cjs +149 -0
  74. package/dist/server.cjs.map +1 -0
  75. package/dist/server.d.cts +102 -0
  76. package/dist/server.d.ts +102 -0
  77. package/dist/server.js +149 -0
  78. package/dist/server.js.map +1 -0
  79. package/dist/{types-CW8qFdAn.d.ts → types-BAdN1RMu.d.ts} +2 -2
  80. package/dist/{types-B_vjMPtM.d.cts → types-C5i3KMgs.d.cts} +2 -2
  81. package/dist/{types-BXSCbl6p.d.cts → types-DP-DiW6f.d.cts} +2 -2
  82. package/dist/{types-C19cQx5D.d.ts → types-DRLUGLtX.d.ts} +2 -2
  83. package/dist/{types-yVmQfby9.d.cts → types-DxlPXihz.d.cts} +1 -1
  84. package/dist/{types-yVmQfby9.d.ts → types-DxlPXihz.d.ts} +1 -1
  85. package/package.json +25 -4
  86. package/dist/SeoHealthDashboard-AEKVVMUR-7NWV3C3Y.js +0 -4
  87. package/dist/SeoHealthDashboard-AEKVVMUR-7NWV3C3Y.js.map +0 -1
  88. package/dist/SeoHealthDashboard-AEKVVMUR-XWKJUH24.cjs +0 -10
  89. package/dist/SeoHealthDashboard-AEKVVMUR-XWKJUH24.cjs.map +0 -1
  90. package/dist/SeoHealthTool-2KKQJLJL.cjs +0 -11
  91. package/dist/SeoHealthTool-2KKQJLJL.cjs.map +0 -1
  92. package/dist/SeoHealthTool-3CUKGADZ.js +0 -5
  93. package/dist/SeoHealthTool-3CUKGADZ.js.map +0 -1
  94. package/dist/SeoPreview-3EXR6FD4.cjs.map +0 -1
  95. package/dist/SeoPreview-4PPAF4NO.js.map +0 -1
  96. package/dist/chunk-254YHUN3.cjs.map +0 -1
  97. package/dist/chunk-27XSYENH.js +0 -2215
  98. package/dist/chunk-27XSYENH.js.map +0 -1
  99. package/dist/chunk-B5IVI5LP.cjs.map +0 -1
  100. package/dist/chunk-BNXMFD3T.cjs +0 -431
  101. package/dist/chunk-BNXMFD3T.cjs.map +0 -1
  102. package/dist/chunk-DDAAVRWG.js.map +0 -1
  103. package/dist/chunk-HDZZQCH7.js.map +0 -1
  104. package/dist/chunk-P6IOWIO2.cjs +0 -480
  105. package/dist/chunk-P6IOWIO2.cjs.map +0 -1
  106. package/dist/chunk-UOCZFYYP.js +0 -407
  107. package/dist/chunk-UOCZFYYP.js.map +0 -1
  108. package/dist/chunk-VR44E62I.js.map +0 -1
  109. package/dist/chunk-WKXHX3GO.js +0 -424
  110. package/dist/chunk-WKXHX3GO.js.map +0 -1
  111. package/dist/chunk-XZDLJ2N6.cjs +0 -2224
  112. package/dist/chunk-XZDLJ2N6.cjs.map +0 -1
  113. package/dist/chunk-ZBHLMQTS.cjs.map +0 -1
package/README.md CHANGED
@@ -8,7 +8,7 @@ Manage SEO fields, social previews, robots directives, canonical URLs, Schema.or
8
8
 
9
9
  <p><a href="https://www.npmjs.com/package/sanity-plugin-seofields"><img src="https://img.shields.io/npm/v/sanity-plugin-seofields.svg?color=10b981&label=npm" alt="npm version" /></a> <a href="https://www.npmjs.com/package/sanity-plugin-seofields"><img src="https://img.shields.io/npm/dm/sanity-plugin-seofields.svg?color=2563eb&label=downloads" alt="npm downloads" /></a> <a href="./LICENSE"><img src="https://img.shields.io/npm/l/sanity-plugin-seofields.svg?color=f59e0b" alt="license" /></a> <a href="https://github.com/hardik-143/sanity-plugin-seofields"><img src="https://img.shields.io/github/stars/hardik-143/sanity-plugin-seofields?style=social" alt="GitHub stars" /></a> <a href="https://www.sanity.io"><img src="https://img.shields.io/badge/Sanity-v3%20%7C%20v4%20%7C%20v5-f03e2f?logo=sanity" alt="Sanity" /></a> <a href="#compatibility"><img src="https://img.shields.io/badge/TypeScript-ready-3178c6?logo=typescript&logoColor=white" alt="TypeScript" /></a></p>
10
10
 
11
- [**Documentation**](https://sanity-plugin-seofields.thehardik.in/docs) &nbsp;•&nbsp; [Quick Start](#quick-start) &nbsp;•&nbsp; [Configuration](#configuration) &nbsp;•&nbsp; [Schema.org](#schemaorg-structured-data) &nbsp;•&nbsp; [CLI](#cli)
11
+ [**Documentation**](https://sanity-plugin-seofields.thehardik.in/docs) &nbsp;•&nbsp; [Quick Start](#quick-start) &nbsp;•&nbsp; [Configuration](#configuration) &nbsp;•&nbsp; [AI](#ai-content-generation) &nbsp;•&nbsp; [Schema.org](#schemaorg-structured-data) &nbsp;•&nbsp; [CLI](#cli)
12
12
 
13
13
  ---
14
14
 
@@ -16,17 +16,18 @@ Manage SEO fields, social previews, robots directives, canonical URLs, Schema.or
16
16
 
17
17
  Most Sanity SEO plugins stop at title and description fields. `sanity-plugin-seofields` gives editors and developers a full SEO workflow — from per-document fields all the way to studio-wide audits.
18
18
 
19
- | Feature | What you get |
20
- | :--------------------- | :----------------------------------------- |
21
- | Structured SEO fields | Complete field group for every document |
22
- | Live SERP preview | See the Google result while editing |
23
- | Open Graph + X/Twitter | Full social card controls |
24
- | Robots + canonical | Indexing directives and canonical URLs |
25
- | Custom meta tags | Reusable `metaTag` / `metaAttribute` types |
26
- | Schema.org JSON-LD | 39 types for structured data |
27
- | Next.js helpers | Metadata and script rendering |
28
- | SEO Health Dashboard | Audit documents across the whole studio |
29
- | CLI | Setup, reports, and exports |
19
+ | Feature | What you get |
20
+ | :--------------------- | :----------------------------------------------------- |
21
+ | Structured SEO fields | Complete field group for every document |
22
+ | Live SERP preview | See the Google result while editing |
23
+ | Open Graph + X/Twitter | Full social card controls |
24
+ | Robots + canonical | Indexing directives and canonical URLs |
25
+ | Custom meta tags | Reusable `metaTag` / `metaAttribute` types |
26
+ | Schema.org JSON-LD | 39 types for structured data |
27
+ | Frontend helpers | Next.js Metadata, React tags, and plain head data |
28
+ | AI content generation | Generate + refine SEO copy, 5 providers, 17 industries |
29
+ | SEO Health Dashboard | Audit documents across the whole studio |
30
+ | CLI | Setup, reports, and exports |
30
31
 
31
32
  Use it as a simple field plugin, a structured data system, or a complete SEO operations layer for your Sanity projects.
32
33
 
@@ -39,8 +40,11 @@ Use it as a simple field plugin, a structured data system, or a complete SEO ope
39
40
  - [Quick Start](#quick-start)
40
41
  - [Registered Schema Types](#registered-schema-types)
41
42
  - [Configuration](#configuration)
43
+ - [AI Content Generation](#ai-content-generation)
42
44
  - [SEO Health Dashboard](#seo-health-dashboard)
43
45
  - [Schema.org Structured Data](#schemaorg-structured-data)
46
+ - [Frontend Integration](#frontend-integration)
47
+ - [Frontend Head Exports](#frontend-head-exports)
44
48
  - [Next.js & React Exports](#nextjs--react-exports)
45
49
  - [CLI](#cli)
46
50
  - [Package Exports](#package-exports)
@@ -75,6 +79,7 @@ Use it as a simple field plugin, a structured data system, or a complete SEO ope
75
79
 
76
80
  - Studio tool for scanning SEO coverage across documents
77
81
  - 0–100 scoring per document
82
+ - Keyword and focus-keyword scoring rewards actual placement/prominence in title and description, not just presence
78
83
  - Missing title, description, image, canonical, robots, and social metadata checks
79
84
  - Document-type filters
80
85
  - Query customization
@@ -105,14 +110,41 @@ Use it as a simple field plugin, a structured data system, or a complete SEO ope
105
110
 
106
111
  <br />
107
112
 
113
+ <img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/nextjs.svg" width="20" height="20" align="center" alt="Next.js"/> Next.js&nbsp;&nbsp;
114
+ <img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/astro.svg" width="20" height="20" align="center" alt="Astro"/> Astro&nbsp;&nbsp;
115
+ <img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/nuxtjs.svg" width="20" height="20" align="center" alt="Nuxt"/> Nuxt&nbsp;&nbsp;
116
+ <img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/vue.svg" width="20" height="20" align="center" alt="Vue"/> Vue&nbsp;&nbsp;
117
+ <img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/svelte.svg" width="20" height="20" align="center" alt="SvelteKit"/> SvelteKit&nbsp;&nbsp;
118
+ Remix
119
+
120
+ <br />
121
+ <br />
122
+
108
123
  - `buildSeoMeta()` for Next.js App Router `generateMetadata()`
109
124
  - `<SeoMetaTags />` for framework-agnostic React rendering
110
- - Schema.org React components for Next.js and other React frameworks
125
+ - `buildSeoHead()` for Astro, Nuxt, Vue, SvelteKit, Remix, and custom head renderers
126
+ - Schema.org React components for Next.js/React plus JSON-LD builders for custom frontends
111
127
  - Image URL resolver hooks for Sanity asset pipelines
112
128
  - Sanitizers for Open Graph type and Twitter Card values
113
129
 
114
130
  </details>
115
131
 
132
+ <details>
133
+ <summary><b>AI Content Generation</b></summary>
134
+
135
+ <br />
136
+
137
+ - "Generate with AI" button for title, description, focus keyword, keywords, Open Graph, and X/Twitter fields
138
+ - 5 providers — OpenAI, Anthropic, Groq, Gemini, Ollama — plus any OpenAI-compatible `baseUrl`
139
+ - 17 industry-specific prompt contexts (8 free, 9 pro) at up to 10 prompt variations per field
140
+ - Secure server-side proxy adapters for Next.js, Express, Node, and Fetch-API runtimes — keeps API keys off the client
141
+ - Per-document-type content source mapping, including nested Portable Text
142
+ - Automatic length, keyword, and readability refinement passes with retry
143
+
144
+ See [AI.md](./AI.md) for full setup, security notes, and configuration reference.
145
+
146
+ </details>
147
+
116
148
  <details>
117
149
  <summary><b>CLI</b></summary>
118
150
 
@@ -134,6 +166,9 @@ Use it as a simple field plugin, a structured data system, or a complete SEO ope
134
166
  npm install sanity-plugin-seofields
135
167
  ```
136
168
 
169
+ This single install also brings in the licensed helper package used internally for pro features.
170
+ You do **not** need to install `seofields-pro` separately.
171
+
137
172
  Peer dependencies:
138
173
 
139
174
  ```txt
@@ -210,6 +245,48 @@ Full guide: [Frontend integration](https://sanity-plugin-seofields.thehardik.in/
210
245
 
211
246
  ---
212
247
 
248
+ ### 4. Render SEO Metadata Outside Next.js
249
+
250
+ Use `buildSeoHead()` when your framework expects plain head tag data instead of
251
+ Next.js `Metadata` or React elements.
252
+
253
+ ```ts
254
+ import {buildSeoHead} from 'sanity-plugin-seofields/head'
255
+
256
+ const head = buildSeoHead({
257
+ seo: page.seo,
258
+ baseUrl: 'https://example.com',
259
+ path: `/${page.slug}`,
260
+ defaults: {
261
+ title: page.title,
262
+ description: 'Default site description',
263
+ siteName: 'My Site',
264
+ },
265
+ imageUrlResolver: (image) => urlFor(image).width(1200).height(630).url(),
266
+ })
267
+ ```
268
+
269
+ `head` is serializable and framework-neutral:
270
+
271
+ ```ts
272
+ {
273
+ title: 'Page title',
274
+ meta: [
275
+ {name: 'description', content: '...'},
276
+ {property: 'og:title', content: '...'},
277
+ {name: 'twitter:card', content: 'summary_large_image'},
278
+ ],
279
+ link: [
280
+ {rel: 'canonical', href: 'https://example.com/page'},
281
+ {rel: 'alternate', hreflang: 'fr-FR', href: 'https://example.com/fr/page'},
282
+ ],
283
+ }
284
+ ```
285
+
286
+ Full guide: [Frontend integration](https://sanity-plugin-seofields.thehardik.in/docs/frontend-integration)
287
+
288
+ ---
289
+
213
290
  ## Registered Schema Types
214
291
 
215
292
  | Type | Purpose |
@@ -244,25 +321,74 @@ seofields({
244
321
  },
245
322
  dashboard: {
246
323
  enabled: true,
247
- licenseKey: process.env.SANITY_STUDIO_SEO_LICENSE_KEY,
248
324
  },
325
+ licenseKey: process.env.SANITY_STUDIO_SEO_LICENSE_KEY,
249
326
  })
250
327
  ```
251
328
 
252
- | Option | Description |
253
- | :-------------------- | :------------------------------------------------------------------- |
254
- | `seoPreview` | Enable or disable the live preview shown inside SEO fields |
255
- | `fieldOverrides` | Customize field titles, descriptions, validation, and field metadata |
256
- | `defaultHiddenFields` | Hide specific SEO fields globally |
257
- | `fieldVisibility` | Hide specific SEO fields for specific document types |
258
- | `fieldGroups` | Customize how fields are grouped in the `seoFields` object |
259
- | `apiVersion` | Sanity API version used by plugin clients |
260
- | `dashboard` | Enable and configure the SEO Health Dashboard tool |
329
+ | Option | Description |
330
+ | :-------------------- | :------------------------------------------------------------------------------------- |
331
+ | `seoPreview` | Enable or disable the live preview shown inside SEO fields |
332
+ | `fieldOverrides` | Customize field titles, descriptions, validation, and field metadata |
333
+ | `defaultHiddenFields` | Hide specific SEO fields globally |
334
+ | `fieldVisibility` | Hide specific SEO fields for specific document types |
335
+ | `fieldGroups` | Customize how fields are grouped in the `seoFields` object |
336
+ | `apiVersion` | Sanity API version used by plugin clients |
337
+ | `dashboard` | Enable and configure the SEO Health Dashboard tool |
338
+ | `licenseKey` | License key for pro features (dashboard, publish gate, pro industries) |
339
+ | `ai` | Enable "Generate with AI" fields — see [AI Content Generation](#ai-content-generation) |
261
340
 
262
341
  Full reference: [Configuration docs](https://sanity-plugin-seofields.thehardik.in/docs/configuration)
263
342
 
264
343
  ---
265
344
 
345
+ ## AI Content Generation
346
+
347
+ Add an `ai` object to the plugin config to show a **Generate with AI** button on `title`, `description`, `focusKeyword`, `keywords`, `ogTitle`, `ogDescription`, `twitterTitle`, and `twitterDescription`.
348
+
349
+ ```ts
350
+ seofields({
351
+ ai: {
352
+ provider: 'openai',
353
+ apiKey: process.env.SANITY_STUDIO_OPENAI_API_KEY,
354
+ industry: 'blog',
355
+ },
356
+ })
357
+ ```
358
+
359
+ **Providers** — `openai` (default), `anthropic`, `groq`, `gemini`, `ollama` (local models, no key required), or any OpenAI-compatible `baseUrl` (DeepSeek, xAI Grok, Azure OpenAI, self-hosted).
360
+
361
+ **API key security** — Sanity Studio is client-side; an `apiKey` set directly is bundled into browser JS and readable by anyone with Studio access, regardless of env-var naming. For production, use `endpoint` with a server-side proxy adapter instead:
362
+
363
+ ```ts
364
+ // Next.js — app/api/seo/generate/route.ts
365
+ import {createNextRouteHandler} from 'sanity-plugin-seofields/server'
366
+
367
+ export const {POST, OPTIONS} = createNextRouteHandler({
368
+ provider: 'openai',
369
+ apiKey: process.env.OPENAI_API_KEY, // stays server-side
370
+ })
371
+ ```
372
+
373
+ `sanity-plugin-seofields/server` also ships `createExpressHandler`, `createNodeHandler`, and `createFetchHandler` (Cloudflare Workers, Deno, Bun). Each wraps the same generation pipeline as direct-provider mode — only the key location changes.
374
+
375
+ **Industries & prompt tiers** — 17 industries, 10 prompt variations per field:
376
+
377
+ | Tier | Industries | Prompts per field |
378
+ | :------- | :------------------------------------------------------------------------------------------------------------ | :--------------------------------------- |
379
+ | Free (8) | `blog`, `restaurant`, `travel`, `ecommerce`, `education`, `fitness`, `hospitality`, `nonprofit` | 4 free, 6 more with a license (10 total) |
380
+ | Pro (9) | `healthcare`, `pharmacy`, `finance`, `realestate`, `saas`, `legal`, `insurance`, `automotive`, `homeServices` | All 10 require a license |
381
+
382
+ Without `industry` set, generation uses generic prompts, which are always free.
383
+
384
+ **Content source** (`ai.content`) — defaults to the document's `body` field. Accepts a single field, an array of fields in priority order, or a per-document-type mapping with a `default` fallback. Nested Portable Text is found automatically.
385
+
386
+ **Refinement pipeline** — each generation attempt is checked for target character length, required keyword presence (injected if missing), readability (simplified if too complex), and focus-keyword verbatim match against existing title/description. Controlled by `maxRetries` and `keepFirstOnValidationFail`.
387
+
388
+ Full guide: [AI.md](./AI.md) &nbsp;•&nbsp; [AI Integration docs](https://sanity-plugin-seofields.thehardik.in/docs/ai)
389
+
390
+ ---
391
+
266
392
  ## SEO Health Dashboard
267
393
 
268
394
  The dashboard is a Studio tool that helps teams find SEO gaps before they ship content.
@@ -275,9 +401,9 @@ import seofields from 'sanity-plugin-seofields'
275
401
  export default defineConfig({
276
402
  plugins: [
277
403
  seofields({
404
+ licenseKey: process.env.SANITY_STUDIO_SEO_LICENSE_KEY,
278
405
  dashboard: {
279
406
  enabled: true,
280
- licenseKey: process.env.SANITY_STUDIO_SEO_LICENSE_KEY,
281
407
  query: {
282
408
  types: ['page', 'post', 'product'],
283
409
  },
@@ -300,7 +426,7 @@ Dashboard capabilities:
300
426
  - Open the exact document that needs updates
301
427
  - Customize document title, subtitle, and preview display
302
428
 
303
- Get a dashboard license: [Get license](https://sanity-plugin-seofields.thehardik.in/get-license)
429
+ Get a license key: [Get license](https://sanity-plugin-seofields.thehardik.in/get-license)
304
430
 
305
431
  Dashboard docs: [SEO Health Dashboard](https://sanity-plugin-seofields.thehardik.in/docs/dashboard)
306
432
 
@@ -409,6 +535,289 @@ Schema.org docs: [Structured data guide](https://sanity-plugin-seofields.thehard
409
535
 
410
536
  ---
411
537
 
538
+ ## Frontend Integration
539
+
540
+ The plugin stores SEO fields under the `seoFields` object. If you are comparing
541
+ examples from other Sanity SEO plugins, map the names carefully:
542
+
543
+ | Common name in other plugins | `sanity-plugin-seofields` field |
544
+ | :--------------------------- | :------------------------------ |
545
+ | `seo.metaTitle` | `seo.title` |
546
+ | `seo.metaDescription` | `seo.description` |
547
+ | `seo.metaImage` | `seo.metaImage` |
548
+ | `seo.twitter.cardType` | `seo.twitter.card` |
549
+ | `seo.hreflang` | `seo.hreflangs[]` |
550
+
551
+ ### Shared GROQ Fragment
552
+
553
+ ```ts
554
+ export const SEO_FRAGMENT = `{
555
+ title,
556
+ seo {
557
+ title,
558
+ description,
559
+ canonicalUrl,
560
+ metaImage { asset-> { url }, alt },
561
+ keywords,
562
+ robots { noIndex, noFollow, noTranslate, noImageIndex },
563
+ hreflangs[] { locale, url },
564
+ openGraph {
565
+ title, description, url, siteName, type,
566
+ imageType, imageUrl,
567
+ image { asset-> { url }, alt }
568
+ },
569
+ twitter {
570
+ card, site, creator, title, description,
571
+ imageType, imageUrl,
572
+ image { asset-> { url }, alt }
573
+ },
574
+ metaAttributes[] { _key, key, type, value }
575
+ }
576
+ }`
577
+
578
+ export const PAGE_QUERY = `
579
+ *[_type == "page" && slug.current == $slug][0] ${SEO_FRAGMENT}
580
+ `
581
+ ```
582
+
583
+ ### <img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/nextjs.svg" width="20" height="20" align="center" alt=""/> Next.js App Router
584
+
585
+ ```tsx
586
+ // app/[slug]/page.tsx
587
+ import type {Metadata} from 'next'
588
+ import {buildSeoMeta} from 'sanity-plugin-seofields/next'
589
+ import {client} from '@/sanity/lib/client'
590
+ import {urlFor} from '@/sanity/lib/image'
591
+ import {PAGE_QUERY} from '@/sanity/lib/queries'
592
+
593
+ export async function generateMetadata(props: {
594
+ params: Promise<{slug: string}>
595
+ }): Promise<Metadata> {
596
+ const {slug} = await props.params
597
+ const page = await client.fetch(PAGE_QUERY, {slug})
598
+
599
+ return buildSeoMeta({
600
+ seo: page?.seo,
601
+ baseUrl: 'https://example.com',
602
+ path: '/' + slug,
603
+ defaults: {
604
+ title: page?.title || 'My Site',
605
+ description: 'Default site description',
606
+ siteName: 'My Site',
607
+ twitterSite: '@mysite',
608
+ },
609
+ imageUrlResolver: (image) => urlFor(image).width(1200).height(630).url(),
610
+ })
611
+ }
612
+ ```
613
+
614
+ ### <img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/astro.svg" width="20" height="20" align="center" alt=""/> Astro
615
+
616
+ ```astro
617
+ --- // src/pages/[slug].astro
618
+ import {buildSeoHead} from 'sanity-plugin-seofields/head'
619
+ import {client} from '@/lib/sanity'
620
+ import Layout from '@/layouts/Layout.astro'
621
+ import {PAGE_QUERY} from '@/lib/queries'
622
+
623
+ const {slug} = Astro.params
624
+ const page = await client.fetch(PAGE_QUERY, {slug})
625
+
626
+ if (!page) return Astro.redirect('/404')
627
+
628
+ const head = buildSeoHead({
629
+ seo: page.seo,
630
+ baseUrl: 'https://example.com',
631
+ path: '/' + slug,
632
+ defaults: {
633
+ title: page.title,
634
+ description: 'Default site description',
635
+ siteName: 'My Site',
636
+ },
637
+ })
638
+ ---
639
+
640
+ <Layout head={head}>
641
+ <h1>{page.title}</h1>
642
+ </Layout>
643
+ ```
644
+
645
+ Render the tags in your layout:
646
+
647
+ ```astro
648
+ --- // src/layouts/Layout.astro
649
+ const {head} = Astro.props
650
+ ---
651
+
652
+ <html lang="en">
653
+ <head>
654
+ <title>{head.title}</title>
655
+ {head.meta.map((tag) =>
656
+ 'property' in tag ? (
657
+ <meta property={tag.property} content={tag.content} />
658
+ ) : (
659
+ <meta name={tag.name} content={tag.content} />
660
+ )
661
+ )}
662
+ {head.link.map((tag) => (
663
+ <link rel={tag.rel} href={tag.href} hreflang={tag.hreflang} />
664
+ ))}
665
+ </head>
666
+ <body>
667
+ <slot />
668
+ </body>
669
+ </html>
670
+ ```
671
+
672
+ ### <img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/nuxtjs.svg" width="20" height="20" align="center" alt=""/> Nuxt 3
673
+
674
+ ```vue
675
+ <!-- pages/[slug].vue -->
676
+ <script setup lang="ts">
677
+ import {buildSeoHead} from 'sanity-plugin-seofields/head'
678
+
679
+ const route = useRoute()
680
+ const page = await useSeoData(route.params.slug as string)
681
+
682
+ const head = buildSeoHead({
683
+ seo: page?.seo,
684
+ baseUrl: 'https://example.com',
685
+ path: '/' + route.params.slug,
686
+ defaults: {
687
+ title: page?.title || 'My Site',
688
+ description: 'Default site description',
689
+ siteName: 'My Site',
690
+ },
691
+ })
692
+
693
+ useHead({
694
+ title: head.title,
695
+ meta: head.meta,
696
+ link: head.link,
697
+ })
698
+ </script>
699
+
700
+ <template>
701
+ <main>
702
+ <h1>{{ page?.title }}</h1>
703
+ </main>
704
+ </template>
705
+ ```
706
+
707
+ ### <img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/vue.svg" width="20" height="20" align="center" alt=""/> Vue 3 Standalone
708
+
709
+ Use `@unhead/vue` or another Vue head manager:
710
+
711
+ ```vue
712
+ <script setup lang="ts">
713
+ import {useHead} from '@unhead/vue'
714
+ import {buildSeoHead} from 'sanity-plugin-seofields/head'
715
+ import {client} from '@/lib/sanity'
716
+ import {PAGE_QUERY} from '@/lib/queries'
717
+
718
+ const props = defineProps<{slug: string}>()
719
+ const page = await client.fetch(PAGE_QUERY, {slug: props.slug})
720
+
721
+ const head = buildSeoHead({
722
+ seo: page?.seo,
723
+ baseUrl: 'https://example.com',
724
+ path: '/' + props.slug,
725
+ defaults: {title: page?.title || 'My Site', siteName: 'My Site'},
726
+ })
727
+
728
+ useHead({
729
+ title: head.title,
730
+ meta: head.meta,
731
+ link: head.link,
732
+ })
733
+ </script>
734
+ ```
735
+
736
+ ### <img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/svelte.svg" width="20" height="20" align="center" alt=""/> SvelteKit
737
+
738
+ ```ts
739
+ // src/routes/[slug]/+page.ts
740
+ import {buildSeoHead} from 'sanity-plugin-seofields/head'
741
+ import type {PageLoad} from './$types'
742
+ import {client} from '$lib/sanity'
743
+ import {PAGE_QUERY} from '$lib/queries'
744
+
745
+ export const load: PageLoad = async ({params}) => {
746
+ const page = await client.fetch(PAGE_QUERY, {slug: params.slug})
747
+
748
+ return {
749
+ page,
750
+ head: buildSeoHead({
751
+ seo: page?.seo,
752
+ baseUrl: 'https://example.com',
753
+ path: '/' + params.slug,
754
+ defaults: {
755
+ title: page?.title || 'My Site',
756
+ description: 'Default site description',
757
+ siteName: 'My Site',
758
+ },
759
+ }),
760
+ }
761
+ }
762
+ ```
763
+
764
+ ```svelte
765
+ <!-- src/routes/[slug]/+page.svelte -->
766
+ <script lang="ts">
767
+ import type {PageData} from './$types'
768
+ export let data: PageData
769
+ </script>
770
+
771
+ <svelte:head>
772
+ {#if data.head.title}
773
+ <title>{data.head.title}</title>
774
+ {/if}
775
+
776
+ {#each data.head.meta as tag}
777
+ {#if 'property' in tag}
778
+ <meta property={tag.property} content={tag.content} />
779
+ {:else}
780
+ <meta name={tag.name} content={tag.content} />
781
+ {/if}
782
+ {/each}
783
+
784
+ {#each data.head.link as tag}
785
+ <link rel={tag.rel} href={tag.href} hreflang={tag.hreflang} />
786
+ {/each}
787
+ </svelte:head>
788
+
789
+ <main>
790
+ <h1>{data.page?.title}</h1>
791
+ </main>
792
+ ```
793
+
794
+ Detailed guide: [Frontend integration](https://sanity-plugin-seofields.thehardik.in/docs/frontend-integration)
795
+
796
+ ---
797
+
798
+ ## Frontend Head Exports
799
+
800
+ Use this entry point for Astro, Nuxt, Vue, SvelteKit, Remix, and any frontend
801
+ that wants plain serializable head data without importing the Studio plugin or
802
+ React helpers.
803
+
804
+ ```ts
805
+ import {
806
+ buildSeoHead,
807
+ buildSeoMeta,
808
+ sanitizeOGType,
809
+ sanitizeTwitterCard,
810
+ } from 'sanity-plugin-seofields/head'
811
+ ```
812
+
813
+ Common usage:
814
+
815
+ - Use `buildSeoHead()` in Astro, Nuxt, Vue, SvelteKit, Remix, and custom renderers
816
+ - Use `buildSeoMeta()` if you want the normalized metadata object but not React components
817
+ - Pass an `imageUrlResolver` when your Sanity image data needs URL building
818
+
819
+ ---
820
+
412
821
  ## Next.js & React Exports
413
822
 
414
823
  ```ts
@@ -458,7 +867,9 @@ CLI docs: [CLI guide](https://sanity-plugin-seofields.thehardik.in/docs/cli)
458
867
  | Import path | Use |
459
868
  | :------------------------------------ | :--------------------------------------------------------------------- |
460
869
  | `sanity-plugin-seofields` | Studio plugin, base schema types, dashboard pane factory, shared types |
461
- | `sanity-plugin-seofields/next` | SEO metadata helpers and Schema.org React components |
870
+ | `sanity-plugin-seofields/head` | Framework-neutral SEO helpers (`buildSeoHead`, `buildSeoMeta`) |
871
+ | `sanity-plugin-seofields/server` | Server-side AI proxy adapters (Next.js, Express, Node, Fetch API) |
872
+ | `sanity-plugin-seofields/next` | Next.js metadata helpers, React meta tags, and Schema.org components |
462
873
  | `sanity-plugin-seofields/schema` | Schema.org Sanity schema plugins and type exports |
463
874
  | `sanity-plugin-seofields/schema/next` | Schema.org React JSON-LD components |
464
875
  | `sanity-plugin-seofields/define-cli` | CLI configuration helper |
@@ -0,0 +1,8 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});require('./chunk-A57XNQC7.cjs');
2
+
3
+ // src/components/SeoHealthTool.tsx
4
+ var _seofieldspro = require('seofields-pro');
5
+
6
+
7
+ exports.default = _seofieldspro.SeoHealthTool;
8
+ //# sourceMappingURL=SeoHealthTool-2COI27KI.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/hardik/GITHUB/sanity-plugin-seofields/npm/dist/SeoHealthTool-2COI27KI.cjs","../src/components/SeoHealthTool.tsx"],"names":[],"mappings":"AAAA,yGAA6B;AAC7B;AACA;ACFA,6CAAuC;ADIvC;AACE;AACF,8CAAC","file":"/Users/hardik/GITHUB/sanity-plugin-seofields/npm/dist/SeoHealthTool-2COI27KI.cjs","sourcesContent":[null,"export {SeoHealthTool as default} from 'seofields-pro'\n"]}
@@ -0,0 +1,8 @@
1
+ import "./chunk-7N4MLTMR.js";
2
+
3
+ // src/components/SeoHealthTool.tsx
4
+ import { SeoHealthTool } from "seofields-pro";
5
+ export {
6
+ SeoHealthTool as default
7
+ };
8
+ //# sourceMappingURL=SeoHealthTool-CWI3KB2V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/SeoHealthTool.tsx"],"sourcesContent":["export {SeoHealthTool as default} from 'seofields-pro'\n"],"mappings":";;;AAAA,SAAyB,qBAAc;","names":[]}