sanity-plugin-seofields 1.8.0 → 1.10.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 +508 -26
- package/dist/SeoHealthTool-2COI27KI.cjs +8 -0
- package/dist/SeoHealthTool-2COI27KI.cjs.map +1 -0
- package/dist/SeoHealthTool-CWI3KB2V.js +8 -0
- package/dist/SeoHealthTool-CWI3KB2V.js.map +1 -0
- package/dist/{SeoPreview-3EXR6FD4.cjs → SeoPreview-LMZAWWOE.cjs} +35 -38
- package/dist/SeoPreview-LMZAWWOE.cjs.map +1 -0
- package/dist/{SeoPreview-4PPAF4NO.js → SeoPreview-UNQBKTQO.js} +14 -11
- package/dist/SeoPreview-UNQBKTQO.js.map +1 -0
- package/dist/{chunk-B5IVI5LP.cjs → chunk-5XTQRILL.cjs} +286 -236
- package/dist/chunk-5XTQRILL.cjs.map +1 -0
- package/dist/{chunk-254YHUN3.cjs → chunk-7N4MLTMR.js} +8 -6
- package/dist/chunk-7N4MLTMR.js.map +1 -0
- package/dist/{chunk-DDAAVRWG.js → chunk-A57XNQC7.cjs} +9 -4
- package/dist/chunk-A57XNQC7.cjs.map +1 -0
- package/dist/chunk-BWLJDK5J.js +624 -0
- package/dist/chunk-BWLJDK5J.js.map +1 -0
- package/dist/chunk-CRCXC45D.js +56 -0
- package/dist/chunk-CRCXC45D.js.map +1 -0
- package/dist/chunk-EUG4MNRC.cjs +713 -0
- package/dist/chunk-EUG4MNRC.cjs.map +1 -0
- package/dist/{chunk-ZBHLMQTS.cjs → chunk-HHO2AKAP.js} +44 -17
- package/dist/chunk-HHO2AKAP.js.map +1 -0
- package/dist/chunk-NLEB47UV.js +713 -0
- package/dist/chunk-NLEB47UV.js.map +1 -0
- package/dist/chunk-OULUDJPI.cjs +254 -0
- package/dist/chunk-OULUDJPI.cjs.map +1 -0
- package/dist/chunk-R2U7JF7U.cjs +624 -0
- package/dist/chunk-R2U7JF7U.cjs.map +1 -0
- package/dist/chunk-XD4HKLEA.js +254 -0
- package/dist/chunk-XD4HKLEA.js.map +1 -0
- package/dist/chunk-XYAZJ3WX.cjs +56 -0
- package/dist/chunk-XYAZJ3WX.cjs.map +1 -0
- package/dist/{chunk-VR44E62I.js → chunk-XZKQWV2H.js} +204 -33
- package/dist/chunk-XZKQWV2H.js.map +1 -0
- package/dist/{chunk-HDZZQCH7.js → chunk-Y46ACXM4.cjs} +45 -5
- package/dist/chunk-Y46ACXM4.cjs.map +1 -0
- package/dist/cli.js +27 -27
- package/dist/{component-DEXwtemT.d.ts → component-CjT1hvxh.d.ts} +2 -2
- package/dist/{component-BzI-LHPw.d.cts → component-WYh0z8as.d.cts} +2 -2
- package/dist/define-cli.cjs +2 -4
- package/dist/define-cli.cjs.map +1 -1
- package/dist/define-cli.js +4 -4
- package/dist/define-cli.js.map +1 -1
- package/dist/head.cjs +22 -0
- package/dist/head.cjs.map +1 -0
- package/dist/head.d.cts +314 -0
- package/dist/head.d.ts +314 -0
- package/dist/head.js +22 -0
- package/dist/head.js.map +1 -0
- package/dist/hreflang-_VbSuWYD.d.cts +29 -0
- package/dist/hreflang-_VbSuWYD.d.ts +29 -0
- package/dist/index.cjs +1072 -368
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -374
- package/dist/index.d.ts +7 -374
- package/dist/index.js +1015 -288
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +212 -455
- package/dist/next.cjs.map +1 -1
- package/dist/next.d.cts +9 -206
- package/dist/next.d.ts +9 -206
- package/dist/next.js +189 -111
- package/dist/next.js.map +1 -1
- package/dist/plugin-HFuWZsJu.d.cts +516 -0
- package/dist/plugin-oQUCim56.d.ts +516 -0
- package/dist/schema/next.cjs +173 -327
- package/dist/schema/next.cjs.map +1 -1
- package/dist/schema/next.d.cts +4 -4
- package/dist/schema/next.d.ts +4 -4
- package/dist/schema/next.js +172 -8
- package/dist/schema/next.js.map +1 -1
- package/dist/schema.cjs +376 -517
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +5 -5
- package/dist/schema.d.ts +5 -5
- package/dist/schema.js +216 -11
- package/dist/schema.js.map +1 -1
- package/dist/server.cjs +149 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +100 -0
- package/dist/server.d.ts +100 -0
- package/dist/server.js +149 -0
- package/dist/server.js.map +1 -0
- package/dist/{types-CW8qFdAn.d.ts → types-BAdN1RMu.d.ts} +2 -2
- package/dist/{types-B_vjMPtM.d.cts → types-C5i3KMgs.d.cts} +2 -2
- package/dist/{types-BXSCbl6p.d.cts → types-DP-DiW6f.d.cts} +2 -2
- package/dist/{types-C19cQx5D.d.ts → types-DRLUGLtX.d.ts} +2 -2
- package/dist/{types-yVmQfby9.d.cts → types-DxlPXihz.d.cts} +1 -1
- package/dist/{types-yVmQfby9.d.ts → types-DxlPXihz.d.ts} +1 -1
- package/package.json +25 -4
- package/dist/SeoHealthDashboard-AEKVVMUR-7NWV3C3Y.js +0 -4
- package/dist/SeoHealthDashboard-AEKVVMUR-7NWV3C3Y.js.map +0 -1
- package/dist/SeoHealthDashboard-AEKVVMUR-XWKJUH24.cjs +0 -10
- package/dist/SeoHealthDashboard-AEKVVMUR-XWKJUH24.cjs.map +0 -1
- package/dist/SeoHealthTool-2KKQJLJL.cjs +0 -11
- package/dist/SeoHealthTool-2KKQJLJL.cjs.map +0 -1
- package/dist/SeoHealthTool-3CUKGADZ.js +0 -5
- package/dist/SeoHealthTool-3CUKGADZ.js.map +0 -1
- package/dist/SeoPreview-3EXR6FD4.cjs.map +0 -1
- package/dist/SeoPreview-4PPAF4NO.js.map +0 -1
- package/dist/chunk-254YHUN3.cjs.map +0 -1
- package/dist/chunk-27XSYENH.js +0 -2215
- package/dist/chunk-27XSYENH.js.map +0 -1
- package/dist/chunk-B5IVI5LP.cjs.map +0 -1
- package/dist/chunk-BNXMFD3T.cjs +0 -431
- package/dist/chunk-BNXMFD3T.cjs.map +0 -1
- package/dist/chunk-DDAAVRWG.js.map +0 -1
- package/dist/chunk-HDZZQCH7.js.map +0 -1
- package/dist/chunk-P6IOWIO2.cjs +0 -480
- package/dist/chunk-P6IOWIO2.cjs.map +0 -1
- package/dist/chunk-UOCZFYYP.js +0 -407
- package/dist/chunk-UOCZFYYP.js.map +0 -1
- package/dist/chunk-VR44E62I.js.map +0 -1
- package/dist/chunk-WKXHX3GO.js +0 -424
- package/dist/chunk-WKXHX3GO.js.map +0 -1
- package/dist/chunk-XZDLJ2N6.cjs +0 -2224
- package/dist/chunk-XZDLJ2N6.cjs.map +0 -1
- 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) • [Quick Start](#quick-start) • [Configuration](#configuration) • [Schema.org](#schemaorg-structured-data) • [CLI](#cli)
|
|
11
|
+
[**Documentation**](https://sanity-plugin-seofields.thehardik.in/docs) • [Quick Start](#quick-start) • [Configuration](#configuration) • [AI](#ai-content-generation) • [Schema.org](#schemaorg-structured-data) • [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
|
|
28
|
-
|
|
|
29
|
-
|
|
|
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
|
|
114
|
+
<img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/astro.svg" width="20" height="20" align="center" alt="Astro"/> Astro
|
|
115
|
+
<img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/nuxtjs.svg" width="20" height="20" align="center" alt="Nuxt"/> Nuxt
|
|
116
|
+
<img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/vue.svg" width="20" height="20" align="center" alt="Vue"/> Vue
|
|
117
|
+
<img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/svelte.svg" width="20" height="20" align="center" alt="SvelteKit"/> SvelteKit
|
|
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
|
-
-
|
|
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,87 @@ 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
|
-
| `
|
|
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
|
+
| `hreflang` | Auto-populate hreflangs from translations — see [Hreflang auto-populate](#hreflang-auto-populate) |
|
|
338
|
+
| `dashboard` | Enable and configure the SEO Health Dashboard tool |
|
|
339
|
+
| `licenseKey` | License key for pro features (dashboard, publish gate, pro industries) |
|
|
340
|
+
| `ai` | Enable "Generate with AI" fields — see [AI Content Generation](#ai-content-generation) |
|
|
261
341
|
|
|
262
342
|
Full reference: [Configuration docs](https://sanity-plugin-seofields.thehardik.in/docs/configuration)
|
|
263
343
|
|
|
264
344
|
---
|
|
265
345
|
|
|
346
|
+
## AI Content Generation
|
|
347
|
+
|
|
348
|
+
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`.
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
seofields({
|
|
352
|
+
ai: {
|
|
353
|
+
provider: 'openai',
|
|
354
|
+
apiKey: process.env.SANITY_STUDIO_OPENAI_API_KEY,
|
|
355
|
+
industry: 'blog',
|
|
356
|
+
},
|
|
357
|
+
})
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Providers** — `openai` (default), `anthropic`, `groq`, `gemini`, `ollama` (local models, no key required), or any OpenAI-compatible `baseUrl` (DeepSeek, xAI Grok, Azure OpenAI, self-hosted).
|
|
361
|
+
|
|
362
|
+
**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:
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
// Next.js — app/api/seo/generate/route.ts
|
|
366
|
+
import {createNextRouteHandler} from 'sanity-plugin-seofields/server'
|
|
367
|
+
|
|
368
|
+
export const {POST, OPTIONS} = createNextRouteHandler({
|
|
369
|
+
provider: 'openai',
|
|
370
|
+
apiKey: process.env.OPENAI_API_KEY, // stays server-side
|
|
371
|
+
})
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
`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.
|
|
375
|
+
|
|
376
|
+
**Industries & prompt tiers** — 17 industries, 10 prompt variations per field:
|
|
377
|
+
|
|
378
|
+
| Tier | Industries | Prompts per field |
|
|
379
|
+
| :------- | :------------------------------------------------------------------------------------------------------------ | :--------------------------------------- |
|
|
380
|
+
| Free (8) | `blog`, `restaurant`, `travel`, `ecommerce`, `education`, `fitness`, `hospitality`, `nonprofit` | 4 free, 6 more with a license (10 total) |
|
|
381
|
+
| Pro (9) | `healthcare`, `pharmacy`, `finance`, `realestate`, `saas`, `legal`, `insurance`, `automotive`, `homeServices` | All 10 require a license |
|
|
382
|
+
|
|
383
|
+
Without `industry` set, generation uses generic prompts, which are always free.
|
|
384
|
+
|
|
385
|
+
**Custom prompts** — write your own prompt wording with `customPrompt` (free: one function) or `customPrompts` (paid: up to 5 generic + 5 per industry, behind a license). Your function receives all extracted document values (`content`, `focusKeyword`, `keywords`, `meta`, `field`, `industry`) and returns the prompt string. Custom prompts replace the built-in pool by default; set `merge: true` to mix them in. In proxy mode set them on the server handler config. See [AI.md → Custom Prompts](./AI.md#custom-prompts).
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
seofields({
|
|
389
|
+
ai: {
|
|
390
|
+
provider: 'openai',
|
|
391
|
+
apiKey: process.env.SANITY_STUDIO_OPENAI_API_KEY,
|
|
392
|
+
customPrompt: (v) => `Write a 55-char SEO ${v.field} about: ${v.content.slice(0, 200)}`,
|
|
393
|
+
},
|
|
394
|
+
})
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**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.
|
|
398
|
+
|
|
399
|
+
**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`.
|
|
400
|
+
|
|
401
|
+
Full guide: [AI.md](./AI.md) • [AI Integration docs](https://sanity-plugin-seofields.thehardik.in/docs/ai)
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
266
405
|
## SEO Health Dashboard
|
|
267
406
|
|
|
268
407
|
The dashboard is a Studio tool that helps teams find SEO gaps before they ship content.
|
|
@@ -275,9 +414,9 @@ import seofields from 'sanity-plugin-seofields'
|
|
|
275
414
|
export default defineConfig({
|
|
276
415
|
plugins: [
|
|
277
416
|
seofields({
|
|
417
|
+
licenseKey: process.env.SANITY_STUDIO_SEO_LICENSE_KEY,
|
|
278
418
|
dashboard: {
|
|
279
419
|
enabled: true,
|
|
280
|
-
licenseKey: process.env.SANITY_STUDIO_SEO_LICENSE_KEY,
|
|
281
420
|
query: {
|
|
282
421
|
types: ['page', 'post', 'product'],
|
|
283
422
|
},
|
|
@@ -300,7 +439,7 @@ Dashboard capabilities:
|
|
|
300
439
|
- Open the exact document that needs updates
|
|
301
440
|
- Customize document title, subtitle, and preview display
|
|
302
441
|
|
|
303
|
-
Get a
|
|
442
|
+
Get a license key: [Get license](https://sanity-plugin-seofields.thehardik.in/get-license)
|
|
304
443
|
|
|
305
444
|
Dashboard docs: [SEO Health Dashboard](https://sanity-plugin-seofields.thehardik.in/docs/dashboard)
|
|
306
445
|
|
|
@@ -409,6 +548,289 @@ Schema.org docs: [Structured data guide](https://sanity-plugin-seofields.thehard
|
|
|
409
548
|
|
|
410
549
|
---
|
|
411
550
|
|
|
551
|
+
## Frontend Integration
|
|
552
|
+
|
|
553
|
+
The plugin stores SEO fields under the `seoFields` object. If you are comparing
|
|
554
|
+
examples from other Sanity SEO plugins, map the names carefully:
|
|
555
|
+
|
|
556
|
+
| Common name in other plugins | `sanity-plugin-seofields` field |
|
|
557
|
+
| :--------------------------- | :------------------------------ |
|
|
558
|
+
| `seo.metaTitle` | `seo.title` |
|
|
559
|
+
| `seo.metaDescription` | `seo.description` |
|
|
560
|
+
| `seo.metaImage` | `seo.metaImage` |
|
|
561
|
+
| `seo.twitter.cardType` | `seo.twitter.card` |
|
|
562
|
+
| `seo.hreflang` | `seo.hreflangs[]` |
|
|
563
|
+
|
|
564
|
+
### Shared GROQ Fragment
|
|
565
|
+
|
|
566
|
+
```ts
|
|
567
|
+
export const SEO_FRAGMENT = `{
|
|
568
|
+
title,
|
|
569
|
+
seo {
|
|
570
|
+
title,
|
|
571
|
+
description,
|
|
572
|
+
canonicalUrl,
|
|
573
|
+
metaImage { asset-> { url }, alt },
|
|
574
|
+
keywords,
|
|
575
|
+
robots { noIndex, noFollow, noTranslate, noImageIndex },
|
|
576
|
+
hreflangs[] { locale, url },
|
|
577
|
+
openGraph {
|
|
578
|
+
title, description, url, siteName, type,
|
|
579
|
+
imageType, imageUrl,
|
|
580
|
+
image { asset-> { url }, alt }
|
|
581
|
+
},
|
|
582
|
+
twitter {
|
|
583
|
+
card, site, creator, title, description,
|
|
584
|
+
imageType, imageUrl,
|
|
585
|
+
image { asset-> { url }, alt }
|
|
586
|
+
},
|
|
587
|
+
metaAttributes[] { _key, key, type, value }
|
|
588
|
+
}
|
|
589
|
+
}`
|
|
590
|
+
|
|
591
|
+
export const PAGE_QUERY = `
|
|
592
|
+
*[_type == "page" && slug.current == $slug][0] ${SEO_FRAGMENT}
|
|
593
|
+
`
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### <img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/nextjs.svg" width="20" height="20" align="center" alt=""/> Next.js App Router
|
|
597
|
+
|
|
598
|
+
```tsx
|
|
599
|
+
// app/[slug]/page.tsx
|
|
600
|
+
import type {Metadata} from 'next'
|
|
601
|
+
import {buildSeoMeta} from 'sanity-plugin-seofields/next'
|
|
602
|
+
import {client} from '@/sanity/lib/client'
|
|
603
|
+
import {urlFor} from '@/sanity/lib/image'
|
|
604
|
+
import {PAGE_QUERY} from '@/sanity/lib/queries'
|
|
605
|
+
|
|
606
|
+
export async function generateMetadata(props: {
|
|
607
|
+
params: Promise<{slug: string}>
|
|
608
|
+
}): Promise<Metadata> {
|
|
609
|
+
const {slug} = await props.params
|
|
610
|
+
const page = await client.fetch(PAGE_QUERY, {slug})
|
|
611
|
+
|
|
612
|
+
return buildSeoMeta({
|
|
613
|
+
seo: page?.seo,
|
|
614
|
+
baseUrl: 'https://example.com',
|
|
615
|
+
path: '/' + slug,
|
|
616
|
+
defaults: {
|
|
617
|
+
title: page?.title || 'My Site',
|
|
618
|
+
description: 'Default site description',
|
|
619
|
+
siteName: 'My Site',
|
|
620
|
+
twitterSite: '@mysite',
|
|
621
|
+
},
|
|
622
|
+
imageUrlResolver: (image) => urlFor(image).width(1200).height(630).url(),
|
|
623
|
+
})
|
|
624
|
+
}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
### <img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/astro.svg" width="20" height="20" align="center" alt=""/> Astro
|
|
628
|
+
|
|
629
|
+
```astro
|
|
630
|
+
--- // src/pages/[slug].astro
|
|
631
|
+
import {buildSeoHead} from 'sanity-plugin-seofields/head'
|
|
632
|
+
import {client} from '@/lib/sanity'
|
|
633
|
+
import Layout from '@/layouts/Layout.astro'
|
|
634
|
+
import {PAGE_QUERY} from '@/lib/queries'
|
|
635
|
+
|
|
636
|
+
const {slug} = Astro.params
|
|
637
|
+
const page = await client.fetch(PAGE_QUERY, {slug})
|
|
638
|
+
|
|
639
|
+
if (!page) return Astro.redirect('/404')
|
|
640
|
+
|
|
641
|
+
const head = buildSeoHead({
|
|
642
|
+
seo: page.seo,
|
|
643
|
+
baseUrl: 'https://example.com',
|
|
644
|
+
path: '/' + slug,
|
|
645
|
+
defaults: {
|
|
646
|
+
title: page.title,
|
|
647
|
+
description: 'Default site description',
|
|
648
|
+
siteName: 'My Site',
|
|
649
|
+
},
|
|
650
|
+
})
|
|
651
|
+
---
|
|
652
|
+
|
|
653
|
+
<Layout head={head}>
|
|
654
|
+
<h1>{page.title}</h1>
|
|
655
|
+
</Layout>
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
Render the tags in your layout:
|
|
659
|
+
|
|
660
|
+
```astro
|
|
661
|
+
--- // src/layouts/Layout.astro
|
|
662
|
+
const {head} = Astro.props
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
<html lang="en">
|
|
666
|
+
<head>
|
|
667
|
+
<title>{head.title}</title>
|
|
668
|
+
{head.meta.map((tag) =>
|
|
669
|
+
'property' in tag ? (
|
|
670
|
+
<meta property={tag.property} content={tag.content} />
|
|
671
|
+
) : (
|
|
672
|
+
<meta name={tag.name} content={tag.content} />
|
|
673
|
+
)
|
|
674
|
+
)}
|
|
675
|
+
{head.link.map((tag) => (
|
|
676
|
+
<link rel={tag.rel} href={tag.href} hreflang={tag.hreflang} />
|
|
677
|
+
))}
|
|
678
|
+
</head>
|
|
679
|
+
<body>
|
|
680
|
+
<slot />
|
|
681
|
+
</body>
|
|
682
|
+
</html>
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### <img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/nuxtjs.svg" width="20" height="20" align="center" alt=""/> Nuxt 3
|
|
686
|
+
|
|
687
|
+
```vue
|
|
688
|
+
<!-- pages/[slug].vue -->
|
|
689
|
+
<script setup lang="ts">
|
|
690
|
+
import {buildSeoHead} from 'sanity-plugin-seofields/head'
|
|
691
|
+
|
|
692
|
+
const route = useRoute()
|
|
693
|
+
const page = await useSeoData(route.params.slug as string)
|
|
694
|
+
|
|
695
|
+
const head = buildSeoHead({
|
|
696
|
+
seo: page?.seo,
|
|
697
|
+
baseUrl: 'https://example.com',
|
|
698
|
+
path: '/' + route.params.slug,
|
|
699
|
+
defaults: {
|
|
700
|
+
title: page?.title || 'My Site',
|
|
701
|
+
description: 'Default site description',
|
|
702
|
+
siteName: 'My Site',
|
|
703
|
+
},
|
|
704
|
+
})
|
|
705
|
+
|
|
706
|
+
useHead({
|
|
707
|
+
title: head.title,
|
|
708
|
+
meta: head.meta,
|
|
709
|
+
link: head.link,
|
|
710
|
+
})
|
|
711
|
+
</script>
|
|
712
|
+
|
|
713
|
+
<template>
|
|
714
|
+
<main>
|
|
715
|
+
<h1>{{ page?.title }}</h1>
|
|
716
|
+
</main>
|
|
717
|
+
</template>
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### <img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/vue.svg" width="20" height="20" align="center" alt=""/> Vue 3 Standalone
|
|
721
|
+
|
|
722
|
+
Use `@unhead/vue` or another Vue head manager:
|
|
723
|
+
|
|
724
|
+
```vue
|
|
725
|
+
<script setup lang="ts">
|
|
726
|
+
import {useHead} from '@unhead/vue'
|
|
727
|
+
import {buildSeoHead} from 'sanity-plugin-seofields/head'
|
|
728
|
+
import {client} from '@/lib/sanity'
|
|
729
|
+
import {PAGE_QUERY} from '@/lib/queries'
|
|
730
|
+
|
|
731
|
+
const props = defineProps<{slug: string}>()
|
|
732
|
+
const page = await client.fetch(PAGE_QUERY, {slug: props.slug})
|
|
733
|
+
|
|
734
|
+
const head = buildSeoHead({
|
|
735
|
+
seo: page?.seo,
|
|
736
|
+
baseUrl: 'https://example.com',
|
|
737
|
+
path: '/' + props.slug,
|
|
738
|
+
defaults: {title: page?.title || 'My Site', siteName: 'My Site'},
|
|
739
|
+
})
|
|
740
|
+
|
|
741
|
+
useHead({
|
|
742
|
+
title: head.title,
|
|
743
|
+
meta: head.meta,
|
|
744
|
+
link: head.link,
|
|
745
|
+
})
|
|
746
|
+
</script>
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### <img src="https://sanity-plugin-seofields.thehardik.in/icons/frameworks/svelte.svg" width="20" height="20" align="center" alt=""/> SvelteKit
|
|
750
|
+
|
|
751
|
+
```ts
|
|
752
|
+
// src/routes/[slug]/+page.ts
|
|
753
|
+
import {buildSeoHead} from 'sanity-plugin-seofields/head'
|
|
754
|
+
import type {PageLoad} from './$types'
|
|
755
|
+
import {client} from '$lib/sanity'
|
|
756
|
+
import {PAGE_QUERY} from '$lib/queries'
|
|
757
|
+
|
|
758
|
+
export const load: PageLoad = async ({params}) => {
|
|
759
|
+
const page = await client.fetch(PAGE_QUERY, {slug: params.slug})
|
|
760
|
+
|
|
761
|
+
return {
|
|
762
|
+
page,
|
|
763
|
+
head: buildSeoHead({
|
|
764
|
+
seo: page?.seo,
|
|
765
|
+
baseUrl: 'https://example.com',
|
|
766
|
+
path: '/' + params.slug,
|
|
767
|
+
defaults: {
|
|
768
|
+
title: page?.title || 'My Site',
|
|
769
|
+
description: 'Default site description',
|
|
770
|
+
siteName: 'My Site',
|
|
771
|
+
},
|
|
772
|
+
}),
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
```svelte
|
|
778
|
+
<!-- src/routes/[slug]/+page.svelte -->
|
|
779
|
+
<script lang="ts">
|
|
780
|
+
import type {PageData} from './$types'
|
|
781
|
+
export let data: PageData
|
|
782
|
+
</script>
|
|
783
|
+
|
|
784
|
+
<svelte:head>
|
|
785
|
+
{#if data.head.title}
|
|
786
|
+
<title>{data.head.title}</title>
|
|
787
|
+
{/if}
|
|
788
|
+
|
|
789
|
+
{#each data.head.meta as tag}
|
|
790
|
+
{#if 'property' in tag}
|
|
791
|
+
<meta property={tag.property} content={tag.content} />
|
|
792
|
+
{:else}
|
|
793
|
+
<meta name={tag.name} content={tag.content} />
|
|
794
|
+
{/if}
|
|
795
|
+
{/each}
|
|
796
|
+
|
|
797
|
+
{#each data.head.link as tag}
|
|
798
|
+
<link rel={tag.rel} href={tag.href} hreflang={tag.hreflang} />
|
|
799
|
+
{/each}
|
|
800
|
+
</svelte:head>
|
|
801
|
+
|
|
802
|
+
<main>
|
|
803
|
+
<h1>{data.page?.title}</h1>
|
|
804
|
+
</main>
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
Detailed guide: [Frontend integration](https://sanity-plugin-seofields.thehardik.in/docs/frontend-integration)
|
|
808
|
+
|
|
809
|
+
---
|
|
810
|
+
|
|
811
|
+
## Frontend Head Exports
|
|
812
|
+
|
|
813
|
+
Use this entry point for Astro, Nuxt, Vue, SvelteKit, Remix, and any frontend
|
|
814
|
+
that wants plain serializable head data without importing the Studio plugin or
|
|
815
|
+
React helpers.
|
|
816
|
+
|
|
817
|
+
```ts
|
|
818
|
+
import {
|
|
819
|
+
buildSeoHead,
|
|
820
|
+
buildSeoMeta,
|
|
821
|
+
sanitizeOGType,
|
|
822
|
+
sanitizeTwitterCard,
|
|
823
|
+
} from 'sanity-plugin-seofields/head'
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
Common usage:
|
|
827
|
+
|
|
828
|
+
- Use `buildSeoHead()` in Astro, Nuxt, Vue, SvelteKit, Remix, and custom renderers
|
|
829
|
+
- Use `buildSeoMeta()` if you want the normalized metadata object but not React components
|
|
830
|
+
- Pass an `imageUrlResolver` when your Sanity image data needs URL building
|
|
831
|
+
|
|
832
|
+
---
|
|
833
|
+
|
|
412
834
|
## Next.js & React Exports
|
|
413
835
|
|
|
414
836
|
```ts
|
|
@@ -431,6 +853,64 @@ Docs: [Frontend integration](https://sanity-plugin-seofields.thehardik.in/docs/f
|
|
|
431
853
|
|
|
432
854
|
---
|
|
433
855
|
|
|
856
|
+
## Hreflang auto-populate
|
|
857
|
+
|
|
858
|
+
For projects using [`@sanity/document-internationalization`](https://github.com/sanity-io/document-internationalization), derive hreflang alternates from your translation references instead of typing them by hand.
|
|
859
|
+
|
|
860
|
+
**Frontend** — `buildHreflangs()` turns the resolved `_translations` array into entries and feeds `buildSeoMeta`:
|
|
861
|
+
|
|
862
|
+
```ts
|
|
863
|
+
import {buildSeoMeta, buildHreflangs} from 'sanity-plugin-seofields/next' // or /head
|
|
864
|
+
|
|
865
|
+
// GROQ: "_translations": *[_type=="translation.metadata" && references(^._id)].translations[].value->{ language, "slug": slug.current }
|
|
866
|
+
export async function generateMetadata() {
|
|
867
|
+
return buildSeoMeta({
|
|
868
|
+
seo: data.seo,
|
|
869
|
+
baseUrl: 'https://example.com',
|
|
870
|
+
path: `/${data.slug.current}`,
|
|
871
|
+
hreflangs: buildHreflangs(data._translations, {
|
|
872
|
+
baseUrl: 'https://example.com',
|
|
873
|
+
xDefault: 'en',
|
|
874
|
+
// resolvePath: (t) => `/${t.language}/${t.slug}`,
|
|
875
|
+
}),
|
|
876
|
+
})
|
|
877
|
+
}
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
**Studio** — enable `hreflang.autoFill` to add a **Sync from translations** button to the `hreflangs` field (entries stay editable):
|
|
881
|
+
|
|
882
|
+
```ts
|
|
883
|
+
seofields({
|
|
884
|
+
baseUrl: 'https://example.com',
|
|
885
|
+
hreflang: {autoFill: true /*, localeField: 'language', resolvePath */},
|
|
886
|
+
})
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
The Studio sync reads `translation.metadata` via the standard client — no extra dependency required.
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
|
|
893
|
+
## llms.txt generator
|
|
894
|
+
|
|
895
|
+
Generate an [llms.txt](https://llmstxt.org) file from your Sanity content with `buildLlmsTxt()` + `docsToLlmsSection()` (framework-neutral, exported from `/head` and `/next`):
|
|
896
|
+
|
|
897
|
+
```ts
|
|
898
|
+
import {buildLlmsTxt, docsToLlmsSection} from 'sanity-plugin-seofields/head'
|
|
899
|
+
|
|
900
|
+
const body = buildLlmsTxt({
|
|
901
|
+
title: 'Acme',
|
|
902
|
+
summary: 'Everything Acme, for humans and LLMs.',
|
|
903
|
+
baseUrl: 'https://acme.com',
|
|
904
|
+
sections: [
|
|
905
|
+
docsToLlmsSection(posts, {title: 'Blog', baseUrl: 'https://acme.com'}),
|
|
906
|
+
docsToLlmsSection(docsPages, {title: 'Docs', baseUrl: 'https://acme.com'}),
|
|
907
|
+
],
|
|
908
|
+
})
|
|
909
|
+
// serve `body` from /llms.txt (route handler or build step)
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
---
|
|
913
|
+
|
|
434
914
|
## CLI
|
|
435
915
|
|
|
436
916
|
Run the CLI:
|
|
@@ -458,7 +938,9 @@ CLI docs: [CLI guide](https://sanity-plugin-seofields.thehardik.in/docs/cli)
|
|
|
458
938
|
| Import path | Use |
|
|
459
939
|
| :------------------------------------ | :--------------------------------------------------------------------- |
|
|
460
940
|
| `sanity-plugin-seofields` | Studio plugin, base schema types, dashboard pane factory, shared types |
|
|
461
|
-
| `sanity-plugin-seofields/
|
|
941
|
+
| `sanity-plugin-seofields/head` | Framework-neutral SEO helpers (`buildSeoHead`, `buildSeoMeta`) |
|
|
942
|
+
| `sanity-plugin-seofields/server` | Server-side AI proxy adapters (Next.js, Express, Node, Fetch API) |
|
|
943
|
+
| `sanity-plugin-seofields/next` | Next.js metadata helpers, React meta tags, and Schema.org components |
|
|
462
944
|
| `sanity-plugin-seofields/schema` | Schema.org Sanity schema plugins and type exports |
|
|
463
945
|
| `sanity-plugin-seofields/schema/next` | Schema.org React JSON-LD components |
|
|
464
946
|
| `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 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/SeoHealthTool.tsx"],"sourcesContent":["export {SeoHealthTool as default} from 'seofields-pro'\n"],"mappings":";;;AAAA,SAAyB,qBAAc;","names":[]}
|