sanity-plugin-seofields 1.2.4 → 1.2.6

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 (49) hide show
  1. package/dist/index.cjs +2604 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.cts +422 -0
  4. package/dist/index.d.ts +339 -492
  5. package/dist/index.js +1284 -2013
  6. package/dist/index.js.map +1 -1
  7. package/dist/next.cjs +182 -0
  8. package/dist/next.cjs.map +1 -0
  9. package/dist/next.d.cts +241 -0
  10. package/dist/next.d.ts +202 -295
  11. package/dist/next.js +110 -70
  12. package/dist/next.js.map +1 -1
  13. package/dist/types-B91ena4g.d.cts +89 -0
  14. package/dist/types-B91ena4g.d.ts +89 -0
  15. package/package.json +37 -18
  16. package/dist/index.d.mts +0 -575
  17. package/dist/index.mjs +0 -3292
  18. package/dist/index.mjs.map +0 -1
  19. package/dist/next.d.mts +0 -334
  20. package/dist/next.mjs +0 -102
  21. package/dist/next.mjs.map +0 -1
  22. package/sanity.json +0 -8
  23. package/src/components/SeoHealthDashboard.tsx +0 -1568
  24. package/src/components/SeoHealthPane.tsx +0 -81
  25. package/src/components/SeoHealthTool.tsx +0 -11
  26. package/src/components/SeoPreview.tsx +0 -178
  27. package/src/components/meta/MetaDescription.tsx +0 -39
  28. package/src/components/meta/MetaTitle.tsx +0 -44
  29. package/src/components/openGraph/OgDescription.tsx +0 -46
  30. package/src/components/openGraph/OgTitle.tsx +0 -45
  31. package/src/components/twitter/twitterDescription.tsx +0 -45
  32. package/src/components/twitter/twitterTitle.tsx +0 -45
  33. package/src/helpers/SeoMetaTags.tsx +0 -154
  34. package/src/helpers/seoMeta.ts +0 -283
  35. package/src/index.ts +0 -26
  36. package/src/next.ts +0 -12
  37. package/src/plugin.ts +0 -344
  38. package/src/schemas/index.ts +0 -121
  39. package/src/schemas/types/index.ts +0 -20
  40. package/src/schemas/types/metaAttribute/index.ts +0 -60
  41. package/src/schemas/types/metaTag/index.ts +0 -17
  42. package/src/schemas/types/openGraph/index.ts +0 -114
  43. package/src/schemas/types/robots/index.ts +0 -26
  44. package/src/schemas/types/twitter/index.ts +0 -108
  45. package/src/types.ts +0 -108
  46. package/src/utils/fieldsUtils.ts +0 -160
  47. package/src/utils/seoUtils.ts +0 -423
  48. package/src/utils/utils.ts +0 -9
  49. package/v2-incompatible.js +0 -11
package/dist/next.d.mts DELETED
@@ -1,334 +0,0 @@
1
- import {default as React_2} from 'react'
2
-
3
- /**
4
- * Convert a Sanity SEO object into a structured metadata object.
5
- *
6
- * The return value is structurally compatible with Next.js App Router's
7
- * `Metadata` type, so you can return it directly from `generateMetadata()`.
8
- *
9
- * @example Next.js App Router
10
- * ```ts
11
- * import { buildSeoMeta } from 'sanity-plugin-seofields'
12
- * import { urlFor } from '@/sanity/lib/image'
13
- *
14
- * export async function generateMetadata(): Promise<Metadata> {
15
- * const { seo } = await sanityFetch({ query: PAGE_SEO_QUERY })
16
- * return buildSeoMeta({
17
- * seo,
18
- * baseUrl: process.env.NEXT_PUBLIC_SITE_URL,
19
- * path: '/about',
20
- * defaults: { title: 'My Site', siteName: 'My Site' },
21
- * imageUrlResolver: (img) => urlFor(img).width(1200).url(),
22
- * })
23
- * }
24
- * ```
25
- */
26
- export declare function buildSeoMeta(options: BuildSeoMetaOptions): SeoMetadata
27
-
28
- /** Options accepted by buildSeoMeta(). */
29
- export declare interface BuildSeoMetaOptions {
30
- /**
31
- * The raw SEO object from Sanity (_type excluded or included — both work).
32
- * Pass `null` or `undefined` to fall back entirely to `defaults`.
33
- *
34
- * Accepts both the strict plugin `SeoFields` type and Sanity's code-generated
35
- * type (which has all nested fields optional) without any `as any` cast.
36
- */
37
- seo?: SeoFieldsInput | null
38
- /**
39
- * The base URL of your site, e.g. "https://example.com".
40
- * Used for canonical URL and OpenGraph URL construction.
41
- */
42
- baseUrl?: string
43
- /**
44
- * The path for the current page, e.g. "/about".
45
- * Combined with baseUrl to produce the canonical + OG url.
46
- * Defaults to "".
47
- */
48
- path?: string
49
- /**
50
- * Default values used when the Sanity SEO fields are empty / missing.
51
- */
52
- defaults?: SeoMetaDefaults
53
- /**
54
- * Resolve a Sanity image asset to a plain URL string.
55
- *
56
- * @example (using @sanity/image-url)
57
- * imageUrlResolver: (img) => urlFor(img).width(1200).url()
58
- */
59
- imageUrlResolver?: (image: SanityImage | SanityImageWithAlt) => string | null | undefined
60
- }
61
-
62
- declare interface MetaAttribute {
63
- _type: 'metaAttribute'
64
- key?: string
65
- type?: 'string' | 'image'
66
- value?: string
67
- image?: SanityImage
68
- }
69
-
70
- declare type OGType = (typeof VALID_OG_TYPES)[number]
71
-
72
- declare interface OpenGraphSettings {
73
- _type: 'openGraph'
74
- /** The canonical URL for OpenGraph (og:url). Maps to the `url` field in Sanity. */
75
- url?: string
76
- title?: string
77
- description?: string
78
- siteName?: string
79
- type?: 'website' | 'article' | 'profile' | 'book' | 'music' | 'video' | 'product'
80
- imageType?: 'upload' | 'url'
81
- image?: SanityImageWithAlt
82
- imageUrl?: string
83
- }
84
-
85
- declare interface RobotsSettings {
86
- noIndex?: boolean
87
- noFollow?: boolean
88
- }
89
-
90
- /**
91
- * Coerce an arbitrary string to a valid OpenGraph type.
92
- * Falls back to "website" when the value is invalid.
93
- */
94
- export declare function sanitizeOGType(value?: string): OGType
95
-
96
- /**
97
- * Coerce an arbitrary string to a valid Twitter card type.
98
- * Falls back to "summary_large_image" when the value is invalid.
99
- */
100
- export declare function sanitizeTwitterCard(value?: string): TwitterCard
101
-
102
- declare interface SanityImage {
103
- _type: 'image'
104
- asset: {
105
- _ref: string
106
- _type: 'reference'
107
- }
108
- hotspot?: {
109
- x: number
110
- y: number
111
- height: number
112
- width: number
113
- }
114
- crop?: {
115
- top: number
116
- bottom: number
117
- left: number
118
- right: number
119
- }
120
- alt?: string
121
- }
122
-
123
- declare interface SanityImageWithAlt extends SanityImage {
124
- alt: string
125
- }
126
-
127
- declare interface SeoFields {
128
- _type: 'seoFields'
129
- robots?: RobotsSettings
130
- preview?: string
131
- title?: string
132
- description?: string
133
- metaImage?: SanityImage
134
- metaAttributes?: MetaAttribute[]
135
- keywords?: string[]
136
- canonicalUrl?: string
137
- openGraph?: OpenGraphSettings
138
- twitter?: TwitterCardSettings
139
- }
140
-
141
- /**
142
- * Input-compatible variant of SeoFields. Structurally matches Sanity's
143
- * code-generated types (where `asset`, `alt`, `key`, and `type` are all
144
- * optional), so you can pass `data.seo` from a sanityFetch result directly
145
- * without any `as any` or manual casting.
146
- */
147
- declare interface SeoFieldsInput {
148
- _type?: string
149
- robots?: {
150
- noIndex?: boolean | null
151
- noFollow?: boolean | null
152
- } | null
153
- title?: string | null
154
- description?: string | null
155
- metaImage?: SeoImageInput | null
156
- metaAttributes?: Array<{
157
- _key?: string
158
- key?: string
159
- value?: string
160
- type?: string
161
- }> | null
162
- keywords?: string[] | null
163
- canonicalUrl?: string | null
164
- openGraph?: {
165
- _type?: string
166
- url?: string | null
167
- title?: string | null
168
- description?: string | null
169
- siteName?: string | null
170
- type?: string | null
171
- imageType?: string | null
172
- image?: SeoImageInput | null
173
- imageUrl?: string | null
174
- } | null
175
- twitter?: {
176
- _type?: string
177
- card?: string | null
178
- site?: string | null
179
- creator?: string | null
180
- title?: string | null
181
- description?: string | null
182
- imageType?: string | null
183
- image?: SeoImageInput | null
184
- imageUrl?: string | null
185
- } | null
186
- }
187
-
188
- /**
189
- * Permissive image shape accepted by buildSeoMeta — compatible with both the
190
- * plugin's SanityImage and Sanity's code-generated image type (where `asset`
191
- * and `alt` are optional).
192
- */
193
- declare interface SeoImageInput {
194
- _type?: string
195
- asset?: {
196
- _ref: string
197
- _type: string
198
- _weak?: boolean
199
- [key: string]: unknown
200
- }
201
- hotspot?: unknown
202
- crop?: unknown
203
- alt?: string
204
- }
205
-
206
- /** Structured metadata returned by buildSeoMeta(). Compatible with Next.js Metadata (App Router). */
207
- export declare interface SeoMetadata {
208
- title?: string | null
209
- description?: string | null
210
- keywords?: string[]
211
- robots?: {
212
- index?: boolean
213
- follow?: boolean
214
- googleBot?: {
215
- index?: boolean
216
- follow?: boolean
217
- }
218
- }
219
- openGraph?: {
220
- type?: string
221
- url?: string
222
- title?: string
223
- description?: string
224
- siteName?: string
225
- images?: Array<{
226
- url: string
227
- width?: number
228
- height?: number
229
- alt?: string
230
- }>
231
- }
232
- twitter?: {
233
- card?: string
234
- site?: string
235
- creator?: string
236
- title?: string
237
- description?: string
238
- images?: string[]
239
- }
240
- alternates?: {
241
- canonical?: string
242
- }
243
- /** Any custom meta attributes from seo.metaAttributes */
244
- other?: Record<string, string>
245
- }
246
-
247
- /** Default values used when SEO fields are missing. */
248
- export declare interface SeoMetaDefaults {
249
- title?: string
250
- description?: string
251
- siteName?: string
252
- twitterSite?: string
253
- twitterCreator?: string
254
- /** Fallback image URL when no OG / Twitter image is set. */
255
- ogImage?: string
256
- }
257
-
258
- /**
259
- * Renders all SEO meta tags for a page as plain React elements.
260
- * Intended to be placed inside your framework's <Head> / <head> component.
261
- *
262
- * Renders:
263
- * - `<title>`
264
- * - `<meta name="description">`
265
- * - `<meta name="keywords">`
266
- * - `<meta name="robots">`
267
- * - OpenGraph meta tags (`og:*`)
268
- * - Twitter Card meta tags (`twitter:*`)
269
- * - Any custom `seo.metaAttributes` as `<meta name="..." content="...">`
270
- */
271
- export declare function SeoMetaTags({
272
- data,
273
- baseUrl,
274
- path,
275
- defaults,
276
- imageUrlResolver,
277
- }: SeoMetaTagsProps): React_2.JSX.Element
278
-
279
- export declare interface SeoMetaTagsProps {
280
- /**
281
- * The raw SEO object from Sanity.
282
- * Pass `null` / `undefined` to render only the defaults.
283
- */
284
- data?: Partial<SeoFields> | null
285
- /**
286
- * Base URL of your site, e.g. "https://example.com".
287
- * Used for canonical link, og:url fallback.
288
- */
289
- baseUrl?: string
290
- /**
291
- * Current page path, e.g. "/about".
292
- * Defaults to "".
293
- */
294
- path?: string
295
- /**
296
- * Default values used when SEO fields are missing.
297
- */
298
- defaults?: BuildSeoMetaOptions['defaults']
299
- /**
300
- * Resolve a Sanity image asset reference to a full URL string.
301
- *
302
- * @example
303
- * imageUrlResolver={(img) => urlFor(img).width(1200).url()}
304
- */
305
- imageUrlResolver?: (image: SanityImage | SanityImageWithAlt) => string | null | undefined
306
- }
307
-
308
- declare type TwitterCard = (typeof VALID_TWITTER_CARDS)[number]
309
-
310
- declare interface TwitterCardSettings {
311
- _type: 'twitter'
312
- card?: 'summary' | 'summary_large_image' | 'app' | 'player'
313
- site?: string
314
- creator?: string
315
- title?: string
316
- description?: string
317
- imageType?: 'upload' | 'url'
318
- image?: SanityImageWithAlt
319
- imageUrl?: string
320
- }
321
-
322
- declare const VALID_OG_TYPES: readonly [
323
- 'website',
324
- 'article',
325
- 'profile',
326
- 'book',
327
- 'music',
328
- 'video',
329
- 'product',
330
- ]
331
-
332
- declare const VALID_TWITTER_CARDS: readonly ['summary', 'summary_large_image', 'app', 'player']
333
-
334
- export {}
package/dist/next.mjs DELETED
@@ -1,102 +0,0 @@
1
- import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
- import r from "react";
3
- const VALID_OG_TYPES = [
4
- "website",
5
- "article",
6
- "profile",
7
- "book",
8
- "music",
9
- "video",
10
- "product"
11
- ];
12
- function sanitizeOGType(value) {
13
- return value && VALID_OG_TYPES.includes(value) ? value : "website";
14
- }
15
- const VALID_TWITTER_CARDS = ["summary", "summary_large_image", "app", "player"];
16
- function sanitizeTwitterCard(value) {
17
- return value && VALID_TWITTER_CARDS.includes(value) ? value : "summary_large_image";
18
- }
19
- function buildSeoMeta(options) {
20
- const { seo, baseUrl = "", path = "", defaults = {}, imageUrlResolver } = options, normalizedBase = baseUrl.replace(/\/+$/, ""), normalizedPath = path.replace(/^\/+/, ""), fullUrl = [normalizedBase, normalizedPath].filter(Boolean).join("/");
21
- let ogImageURL = defaults.ogImage || "";
22
- seo?.openGraph?.imageType === "url" && seo.openGraph.imageUrl ? ogImageURL = seo.openGraph.imageUrl : seo?.openGraph?.image && imageUrlResolver && (ogImageURL = imageUrlResolver(seo.openGraph.image) || ogImageURL);
23
- let twitterImageURL = ogImageURL;
24
- seo?.twitter?.imageType === "url" && seo.twitter.imageUrl ? twitterImageURL = seo.twitter.imageUrl : seo?.twitter?.image && imageUrlResolver && (twitterImageURL = imageUrlResolver(seo.twitter.image) || twitterImageURL);
25
- const other = {};
26
- if (Array.isArray(seo?.metaAttributes))
27
- for (const attr of seo.metaAttributes)
28
- attr.key && attr.value && (other[attr.key] = attr.value);
29
- const ogUrl = seo?.openGraph?.url || fullUrl;
30
- return {
31
- title: seo?.title ?? defaults.title ?? null,
32
- description: seo?.description ?? defaults.description ?? null,
33
- keywords: seo?.keywords?.length ? seo.keywords : void 0,
34
- robots: {
35
- index: !seo?.robots?.noIndex,
36
- follow: !seo?.robots?.noFollow,
37
- googleBot: {
38
- index: !seo?.robots?.noIndex,
39
- follow: !seo?.robots?.noFollow
40
- }
41
- },
42
- openGraph: {
43
- type: sanitizeOGType(seo?.openGraph?.type ?? void 0),
44
- url: ogUrl || void 0,
45
- title: seo?.openGraph?.title ?? defaults.title,
46
- description: seo?.openGraph?.description ?? defaults.description,
47
- siteName: seo?.openGraph?.siteName ?? defaults.siteName,
48
- images: ogImageURL ? [{ url: ogImageURL }] : []
49
- },
50
- twitter: {
51
- card: sanitizeTwitterCard(seo?.twitter?.card ?? void 0),
52
- site: seo?.twitter?.site ?? defaults.twitterSite,
53
- creator: seo?.twitter?.creator ?? defaults.twitterCreator,
54
- title: seo?.twitter?.title ?? defaults.title,
55
- description: seo?.twitter?.description ?? defaults.description,
56
- images: twitterImageURL ? [twitterImageURL] : []
57
- },
58
- alternates: {
59
- canonical: fullUrl || void 0
60
- },
61
- ...Object.keys(other).length > 0 ? { other } : {}
62
- };
63
- }
64
- function SeoMetaTags({ data, baseUrl, path, defaults, imageUrlResolver }) {
65
- const meta = buildSeoMeta({ seo: data, baseUrl, path, defaults, imageUrlResolver }), robotsContent = [
66
- meta.robots?.index === !1 ? "noindex" : "index",
67
- meta.robots?.follow === !1 ? "nofollow" : "follow"
68
- ].join(", ");
69
- return /* @__PURE__ */ jsxs(Fragment, { children: [
70
- meta.title && /* @__PURE__ */ jsx("title", { children: meta.title }),
71
- meta.description && /* @__PURE__ */ jsx("meta", { name: "description", content: meta.description }),
72
- meta.keywords?.length ? /* @__PURE__ */ jsx("meta", { name: "keywords", content: meta.keywords.join(", ") }) : null,
73
- /* @__PURE__ */ jsx("meta", { name: "robots", content: robotsContent }),
74
- /* @__PURE__ */ jsx("meta", { name: "googlebot", content: robotsContent }),
75
- meta.openGraph?.type && /* @__PURE__ */ jsx("meta", { property: "og:type", content: meta.openGraph.type }),
76
- meta.openGraph?.url && /* @__PURE__ */ jsx("meta", { property: "og:url", content: meta.openGraph.url }),
77
- meta.openGraph?.title && /* @__PURE__ */ jsx("meta", { property: "og:title", content: meta.openGraph.title }),
78
- meta.openGraph?.description && /* @__PURE__ */ jsx("meta", { property: "og:description", content: meta.openGraph.description }),
79
- meta.openGraph?.siteName && /* @__PURE__ */ jsx("meta", { property: "og:site_name", content: meta.openGraph.siteName }),
80
- meta.openGraph?.images?.map((img, i) => /* @__PURE__ */ jsxs(r.Fragment, { children: [
81
- /* @__PURE__ */ jsx("meta", { property: "og:image", content: img.url }),
82
- img.width && /* @__PURE__ */ jsx("meta", { property: "og:image:width", content: String(img.width) }),
83
- img.height && /* @__PURE__ */ jsx("meta", { property: "og:image:height", content: String(img.height) }),
84
- img.alt && /* @__PURE__ */ jsx("meta", { property: "og:image:alt", content: img.alt })
85
- ] }, `og-img-${i}`)),
86
- meta.twitter?.card && /* @__PURE__ */ jsx("meta", { name: "twitter:card", content: meta.twitter.card }),
87
- meta.twitter?.site && /* @__PURE__ */ jsx("meta", { name: "twitter:site", content: meta.twitter.site }),
88
- meta.twitter?.creator && /* @__PURE__ */ jsx("meta", { name: "twitter:creator", content: meta.twitter.creator }),
89
- meta.twitter?.title && /* @__PURE__ */ jsx("meta", { name: "twitter:title", content: meta.twitter.title }),
90
- meta.twitter?.description && /* @__PURE__ */ jsx("meta", { name: "twitter:description", content: meta.twitter.description }),
91
- meta.twitter?.images?.map((url, i) => /* @__PURE__ */ jsx("meta", { name: "twitter:image", content: url }, `tw-img-${i}`)),
92
- meta.other && Object.entries(meta.other).map(([name, content]) => /* @__PURE__ */ jsx("meta", { name, content }, `custom-${name}`)),
93
- meta.alternates?.canonical && /* @__PURE__ */ jsx("link", { rel: "canonical", href: meta.alternates.canonical })
94
- ] });
95
- }
96
- export {
97
- SeoMetaTags,
98
- buildSeoMeta,
99
- sanitizeOGType,
100
- sanitizeTwitterCard
101
- };
102
- //# sourceMappingURL=next.mjs.map
package/dist/next.mjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"next.mjs","sources":["../src/helpers/seoMeta.ts","../src/helpers/SeoMetaTags.tsx"],"sourcesContent":["/**\n * Headless CMS integration helpers for sanity-plugin-seofields\n *\n * Provides framework-agnostic SEO metadata utilities for use with:\n * - Next.js App Router → buildSeoMeta() inside generateMetadata()\n * - Next.js Pages Router → <SeoMetaTags> inside Next.js <Head>\n * - Nuxt / Remix / any SSR → <SeoMetaTags> inside your <head> slot\n */\n\nimport type {SanityImage, SanityImageWithAlt, SeoFields} from '../types'\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\n/** Structured metadata returned by buildSeoMeta(). Compatible with Next.js Metadata (App Router). */\nexport interface SeoMetadata {\n title?: string | null\n description?: string | null\n keywords?: string[]\n robots?: {\n index?: boolean\n follow?: boolean\n googleBot?: {\n index?: boolean\n follow?: boolean\n }\n }\n openGraph?: {\n type?: string\n url?: string\n title?: string\n description?: string\n siteName?: string\n images?: Array<{url: string; width?: number; height?: number; alt?: string}>\n }\n twitter?: {\n card?: string\n site?: string\n creator?: string\n title?: string\n description?: string\n images?: string[]\n }\n alternates?: {\n canonical?: string\n }\n /** Any custom meta attributes from seo.metaAttributes */\n other?: Record<string, string>\n}\n\n/** Default values used when SEO fields are missing. */\nexport interface SeoMetaDefaults {\n title?: string\n description?: string\n siteName?: string\n twitterSite?: string\n twitterCreator?: string\n /** Fallback image URL when no OG / Twitter image is set. */\n ogImage?: string\n}\n\n/**\n * Permissive image shape accepted by buildSeoMeta — compatible with both the\n * plugin's SanityImage and Sanity's code-generated image type (where `asset`\n * and `alt` are optional).\n */\ninterface SeoImageInput {\n _type?: string\n asset?: {_ref: string; _type: string; _weak?: boolean; [key: string]: unknown}\n hotspot?: unknown\n crop?: unknown\n alt?: string\n}\n\n/**\n * Input-compatible variant of SeoFields. Structurally matches Sanity's\n * code-generated types (where `asset`, `alt`, `key`, and `type` are all\n * optional), so you can pass `data.seo` from a sanityFetch result directly\n * without any `as any` or manual casting.\n */\nexport interface SeoFieldsInput {\n _type?: string\n robots?: {noIndex?: boolean | null; noFollow?: boolean | null} | null\n title?: string | null\n description?: string | null\n metaImage?: SeoImageInput | null\n metaAttributes?: Array<{_key?: string; key?: string; value?: string; type?: string}> | null\n keywords?: string[] | null\n canonicalUrl?: string | null\n openGraph?: {\n _type?: string\n url?: string | null\n title?: string | null\n description?: string | null\n siteName?: string | null\n type?: string | null\n imageType?: string | null\n image?: SeoImageInput | null\n imageUrl?: string | null\n } | null\n twitter?: {\n _type?: string\n card?: string | null\n site?: string | null\n creator?: string | null\n title?: string | null\n description?: string | null\n imageType?: string | null\n image?: SeoImageInput | null\n imageUrl?: string | null\n } | null\n}\n\n/** Options accepted by buildSeoMeta(). */\nexport interface BuildSeoMetaOptions {\n /**\n * The raw SEO object from Sanity (_type excluded or included — both work).\n * Pass `null` or `undefined` to fall back entirely to `defaults`.\n *\n * Accepts both the strict plugin `SeoFields` type and Sanity's code-generated\n * type (which has all nested fields optional) without any `as any` cast.\n */\n seo?: SeoFieldsInput | null\n\n /**\n * The base URL of your site, e.g. \"https://example.com\".\n * Used for canonical URL and OpenGraph URL construction.\n */\n baseUrl?: string\n\n /**\n * The path for the current page, e.g. \"/about\".\n * Combined with baseUrl to produce the canonical + OG url.\n * Defaults to \"\".\n */\n path?: string\n\n /**\n * Default values used when the Sanity SEO fields are empty / missing.\n */\n defaults?: SeoMetaDefaults\n\n /**\n * Resolve a Sanity image asset to a plain URL string.\n *\n * @example (using @sanity/image-url)\n * imageUrlResolver: (img) => urlFor(img).width(1200).url()\n */\n imageUrlResolver?: (image: SanityImage | SanityImageWithAlt) => string | null | undefined\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────────\n\nconst VALID_OG_TYPES = [\n 'website',\n 'article',\n 'profile',\n 'book',\n 'music',\n 'video',\n 'product',\n] as const\ntype OGType = (typeof VALID_OG_TYPES)[number]\n\n/**\n * Coerce an arbitrary string to a valid OpenGraph type.\n * Falls back to \"website\" when the value is invalid.\n */\nexport function sanitizeOGType(value?: string): OGType {\n if (value && (VALID_OG_TYPES as readonly string[]).includes(value)) {\n return value as OGType\n }\n return 'website'\n}\n\nconst VALID_TWITTER_CARDS = ['summary', 'summary_large_image', 'app', 'player'] as const\ntype TwitterCard = (typeof VALID_TWITTER_CARDS)[number]\n\n/**\n * Coerce an arbitrary string to a valid Twitter card type.\n * Falls back to \"summary_large_image\" when the value is invalid.\n */\nexport function sanitizeTwitterCard(value?: string): TwitterCard {\n if (value && (VALID_TWITTER_CARDS as readonly string[]).includes(value)) {\n return value as TwitterCard\n }\n return 'summary_large_image'\n}\n\n// ─── Core builder ─────────────────────────────────────────────────────────────\n\n/**\n * Convert a Sanity SEO object into a structured metadata object.\n *\n * The return value is structurally compatible with Next.js App Router's\n * `Metadata` type, so you can return it directly from `generateMetadata()`.\n *\n * @example Next.js App Router\n * ```ts\n * import { buildSeoMeta } from 'sanity-plugin-seofields'\n * import { urlFor } from '@/sanity/lib/image'\n *\n * export async function generateMetadata(): Promise<Metadata> {\n * const { seo } = await sanityFetch({ query: PAGE_SEO_QUERY })\n * return buildSeoMeta({\n * seo,\n * baseUrl: process.env.NEXT_PUBLIC_SITE_URL,\n * path: '/about',\n * defaults: { title: 'My Site', siteName: 'My Site' },\n * imageUrlResolver: (img) => urlFor(img).width(1200).url(),\n * })\n * }\n * ```\n */\nexport function buildSeoMeta(options: BuildSeoMetaOptions): SeoMetadata {\n const {seo, baseUrl = '', path = '', defaults = {}, imageUrlResolver} = options\n\n const normalizedBase = baseUrl.replace(/\\/+$/, '') // remove trailing /\n const normalizedPath = path.replace(/^\\/+/, '') // remove leading /\n\n const fullUrl = [normalizedBase, normalizedPath].filter(Boolean).join('/')\n\n // ── OG image resolution ──\n let ogImageURL: string = defaults.ogImage || ''\n if (seo?.openGraph?.imageType === 'url' && seo.openGraph.imageUrl) {\n ogImageURL = seo.openGraph.imageUrl\n } else if (seo?.openGraph?.image && imageUrlResolver) {\n ogImageURL = imageUrlResolver(seo.openGraph.image as SanityImage) || ogImageURL\n }\n\n // ── Twitter image resolution ──\n let twitterImageURL: string = ogImageURL // reuse OG image as fallback\n if (seo?.twitter?.imageType === 'url' && seo.twitter.imageUrl) {\n twitterImageURL = seo.twitter.imageUrl\n } else if (seo?.twitter?.image && imageUrlResolver) {\n twitterImageURL = imageUrlResolver(seo.twitter.image as SanityImage) || twitterImageURL\n }\n\n // ── Custom meta attributes → `other` map ──\n const other: Record<string, string> = {}\n if (Array.isArray(seo?.metaAttributes)) {\n for (const attr of seo!.metaAttributes!) {\n if (attr.key && attr.value) {\n other[attr.key] = attr.value\n }\n }\n }\n\n const ogUrl = seo?.openGraph?.url || fullUrl\n\n return {\n title: seo?.title ?? defaults.title ?? null,\n description: seo?.description ?? defaults.description ?? null,\n keywords: seo?.keywords?.length ? (seo.keywords as string[]) : undefined,\n robots: {\n index: !seo?.robots?.noIndex,\n follow: !seo?.robots?.noFollow,\n googleBot: {\n index: !seo?.robots?.noIndex,\n follow: !seo?.robots?.noFollow,\n },\n },\n openGraph: {\n type: sanitizeOGType(seo?.openGraph?.type ?? undefined),\n url: ogUrl || undefined,\n title: seo?.openGraph?.title ?? defaults.title,\n description: seo?.openGraph?.description ?? defaults.description,\n siteName: seo?.openGraph?.siteName ?? defaults.siteName,\n images: ogImageURL ? [{url: ogImageURL}] : [],\n },\n twitter: {\n card: sanitizeTwitterCard(seo?.twitter?.card ?? undefined),\n site: seo?.twitter?.site ?? defaults.twitterSite,\n creator: seo?.twitter?.creator ?? defaults.twitterCreator,\n title: seo?.twitter?.title ?? defaults.title,\n description: seo?.twitter?.description ?? defaults.description,\n images: twitterImageURL ? [twitterImageURL] : [],\n },\n alternates: {\n canonical: fullUrl || undefined,\n },\n ...(Object.keys(other).length > 0 ? {other} : {}),\n }\n}\n","/**\n * <SeoMetaTags> — Framework-agnostic React SEO meta tag renderer.\n *\n * Renders all SEO meta tags as plain React elements.\n * Place it inside your framework's <Head> component:\n *\n * @example Next.js Pages Router\n * ```tsx\n * import Head from 'next/head'\n * import { SeoMetaTags } from 'sanity-plugin-seofields'\n *\n * export default function Page({ seo }) {\n * return (\n * <>\n * <Head>\n * <SeoMetaTags\n * data={seo}\n * baseUrl=\"https://example.com\"\n * path=\"/about\"\n * defaults={{ title: 'My Site', siteName: 'My Site' }}\n * imageUrlResolver={(img) => urlFor(img).width(1200).url()}\n * />\n * </Head>\n * <main>...</main>\n * </>\n * )\n * }\n * ```\n *\n * @example Nuxt 3 / generic SSR (inside <Head> slot)\n * ```tsx\n * <Head>\n * <SeoMetaTags data={seo} baseUrl=\"https://example.com\" path=\"/\" />\n * </Head>\n * ```\n */\nimport React from 'react'\n\nimport type {SanityImage, SanityImageWithAlt, SeoFields} from '../types'\nimport {buildSeoMeta, type BuildSeoMetaOptions} from './seoMeta'\n\n// ─── Props ────────────────────────────────────────────────────────────────────\n\nexport interface SeoMetaTagsProps {\n /**\n * The raw SEO object from Sanity.\n * Pass `null` / `undefined` to render only the defaults.\n */\n data?: Partial<SeoFields> | null\n\n /**\n * Base URL of your site, e.g. \"https://example.com\".\n * Used for canonical link, og:url fallback.\n */\n baseUrl?: string\n\n /**\n * Current page path, e.g. \"/about\".\n * Defaults to \"\".\n */\n path?: string\n\n /**\n * Default values used when SEO fields are missing.\n */\n defaults?: BuildSeoMetaOptions['defaults']\n\n /**\n * Resolve a Sanity image asset reference to a full URL string.\n *\n * @example\n * imageUrlResolver={(img) => urlFor(img).width(1200).url()}\n */\n imageUrlResolver?: (image: SanityImage | SanityImageWithAlt) => string | null | undefined\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * Renders all SEO meta tags for a page as plain React elements.\n * Intended to be placed inside your framework's <Head> / <head> component.\n *\n * Renders:\n * - `<title>`\n * - `<meta name=\"description\">`\n * - `<meta name=\"keywords\">`\n * - `<meta name=\"robots\">`\n * - OpenGraph meta tags (`og:*`)\n * - Twitter Card meta tags (`twitter:*`)\n * - Any custom `seo.metaAttributes` as `<meta name=\"...\" content=\"...\">`\n */\nexport function SeoMetaTags({data, baseUrl, path, defaults, imageUrlResolver}: SeoMetaTagsProps) {\n const meta = buildSeoMeta({seo: data, baseUrl, path, defaults, imageUrlResolver})\n\n const robotsContent = [\n meta.robots?.index === false ? 'noindex' : 'index',\n meta.robots?.follow === false ? 'nofollow' : 'follow',\n ].join(', ')\n\n return (\n <>\n {/* ── Title ── */}\n {meta.title && <title>{meta.title}</title>}\n\n {/* ── Basic meta ── */}\n {meta.description && <meta name=\"description\" content={meta.description} />}\n {meta.keywords?.length ? <meta name=\"keywords\" content={meta.keywords.join(', ')} /> : null}\n <meta name=\"robots\" content={robotsContent} />\n <meta name=\"googlebot\" content={robotsContent} />\n\n {/* ── Open Graph ── */}\n {meta.openGraph?.type && <meta property=\"og:type\" content={meta.openGraph.type} />}\n {meta.openGraph?.url && <meta property=\"og:url\" content={meta.openGraph.url} />}\n {meta.openGraph?.title && <meta property=\"og:title\" content={meta.openGraph.title} />}\n {meta.openGraph?.description && (\n <meta property=\"og:description\" content={meta.openGraph.description} />\n )}\n {meta.openGraph?.siteName && (\n <meta property=\"og:site_name\" content={meta.openGraph.siteName} />\n )}\n {meta.openGraph?.images?.map((img, i) => (\n <React.Fragment key={`og-img-${i}`}>\n <meta property=\"og:image\" content={img.url} />\n {img.width && <meta property=\"og:image:width\" content={String(img.width)} />}\n {img.height && <meta property=\"og:image:height\" content={String(img.height)} />}\n {img.alt && <meta property=\"og:image:alt\" content={img.alt} />}\n </React.Fragment>\n ))}\n\n {/* ── Twitter Card ── */}\n {meta.twitter?.card && <meta name=\"twitter:card\" content={meta.twitter.card} />}\n {meta.twitter?.site && <meta name=\"twitter:site\" content={meta.twitter.site} />}\n {meta.twitter?.creator && <meta name=\"twitter:creator\" content={meta.twitter.creator} />}\n {meta.twitter?.title && <meta name=\"twitter:title\" content={meta.twitter.title} />}\n {meta.twitter?.description && (\n <meta name=\"twitter:description\" content={meta.twitter.description} />\n )}\n {meta.twitter?.images?.map((url, i) => (\n <meta key={`tw-img-${i}`} name=\"twitter:image\" content={url} />\n ))}\n\n {/* ── Custom meta attributes ── */}\n {meta.other &&\n Object.entries(meta.other).map(([name, content]) => (\n <meta key={`custom-${name}`} name={name} content={content} />\n ))}\n\n {/* ── Canonical URL ── */}\n {meta.alternates?.canonical && <link rel=\"canonical\" href={meta.alternates.canonical} />}\n </>\n )\n}\n\nexport default SeoMetaTags\n"],"names":["React"],"mappings":"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"}
package/sanity.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "parts": [
3
- {
4
- "implements": "part:@sanity/base/sanity-root",
5
- "path": "./v2-incompatible.js"
6
- }
7
- ]
8
- }