sanity-plugin-seofields 1.3.1 → 1.4.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 CHANGED
@@ -482,11 +482,9 @@ seofields({
482
482
  description: 'Track SEO quality across all published content.',
483
483
  },
484
484
 
485
- // Table columns
486
- display: {
487
- typeColumn: true, // show document type column (default: true)
488
- documentId: false, // show document _id (default: true)
489
- },
485
+ // Table columns (flat keys — replaces the deprecated display.* object)
486
+ showTypeColumn: true, // show document type column (default: true)
487
+ showDocumentId: false, // show document _id under titles (default: true)
490
488
 
491
489
  // Document query
492
490
  query: {
@@ -496,6 +494,14 @@ seofields({
496
494
  // ^ custom GROQ takes precedence over types + requireSeo
497
495
  },
498
496
 
497
+ // Human-readable labels for document type names
498
+ typeDisplayLabels: {productDrug: 'Products', landingPage: 'Landing Page'},
499
+
500
+ // Custom badge next to the document title
501
+ getDocumentBadge: (doc) => {
502
+ if (doc.status === 'draft') return {label: 'Draft', bgColor: '#f3f4f6', textColor: '#6b7280'}
503
+ },
504
+
499
505
  apiVersion: '2023-01-01', // Sanity API version (default: '2023-01-01')
500
506
  },
501
507
  })
@@ -506,6 +512,21 @@ seofields({
506
512
  })
507
513
  ```
508
514
 
515
+ ### Deprecated keys (v1.3.2)
516
+
517
+ The following keys were renamed in **v1.3.2** for clarity. The old keys still work but will print a console warning and show an amber banner inside the dashboard. They will be removed in a future major release.
518
+
519
+ | Deprecated (old) | Replacement (new) |
520
+ | ------------------------------------ | ----------------------------------- |
521
+ | `healthDashboard.display.typeColumn` | `healthDashboard.showTypeColumn` |
522
+ | `healthDashboard.display.documentId` | `healthDashboard.showDocumentId` |
523
+ | `healthDashboard.typeLabels` | `healthDashboard.typeDisplayLabels` |
524
+ | `healthDashboard.docBadge` | `healthDashboard.getDocumentBadge` |
525
+
526
+ The same renames apply to `SeoHealthDashboardProps` when using `createSeoHealthPane` directly.
527
+
528
+ See the [v1.3.2 changelog](./CHANGELOG.md#132--2026-03-23) for the full migration diff.
529
+
509
530
  ### What it shows
510
531
 
511
532
  | Feature | Details |
@@ -583,13 +604,13 @@ export default defineConfig({
583
604
 
584
605
  ### `createSeoHealthPane` options
585
606
 
586
- | Option | Type | Default | Description |
587
- | ------------ | --------- | -------------- | --------------------------------------------------------------------- |
588
- | `licenseKey` | `string` | **required** | License key (format `SEOF-XXXX-XXXX-XXXX`). |
589
- | `query` | `string` | — | GROQ query. Must return `_id`, `_type`, `title`, `seo`, `_updatedAt`. |
590
- | `title` | `string` | `'SEO Health'` | Pane title shown in breadcrumb |
607
+ | Option | Type | Default | Description |
608
+ | ------------ | --------- | -------------- | ---------------------------------------------------------------------- |
609
+ | `licenseKey` | `string` | **required** | License key (format `SEOF-XXXX-XXXX-XXXX`). |
610
+ | `query` | `string` | — | GROQ query. Must return `_id`, `_type`, `title`, `seo`, `_updatedAt`. |
611
+ | `title` | `string` | `'SEO Health'` | Pane title shown in breadcrumb |
591
612
  | `openInPane` | `boolean` | `true` | Enable row links that open the document editor as a pane to the right. |
592
- | `...rest` | — | — | All other `SeoHealthDashboardProps` |
613
+ | `...rest` | — | — | All other `SeoHealthDashboardProps` |
593
614
 
594
615
  ## 🌐 Frontend Integration
595
616
 
@@ -678,13 +699,13 @@ export const loader: LoaderFunction = async ({params}) => {
678
699
  }`,
679
700
  {slug: params.slug},
680
701
  )
681
-
702
+
682
703
  // Use buildSeoMeta to generate meta tags
683
704
  const seoMeta = buildSeoMeta(post.seo, {
684
705
  defaultTitle: 'Blog',
685
706
  siteUrl: 'https://example.com',
686
707
  })
687
-
708
+
688
709
  return json({post, seoMeta})
689
710
  }
690
711
 
@@ -711,9 +732,9 @@ export const useSanityMeta = (seo: SEOFields, options = {}) => {
711
732
  defaultTitle = 'My Site',
712
733
  siteUrl = 'https://example.com',
713
734
  } = options
714
-
735
+
715
736
  const meta = buildSeoMeta(seo, {defaultTitle, siteUrl})
716
-
737
+
717
738
  // useHead() handles SSR + client-side rendering
718
739
  useHead({
719
740
  title: seo?.title || defaultTitle,
@@ -721,7 +742,7 @@ export const useSanityMeta = (seo: SEOFields, options = {}) => {
721
742
  name: m.name || m.property,
722
743
  content: m.content,
723
744
  })),
724
- link: seo?.canonicalUrl
745
+ link: seo?.canonicalUrl
725
746
  ? [{rel: 'canonical', href: seo.canonicalUrl}]
726
747
  : [],
727
748
  })
@@ -803,17 +824,17 @@ export function PostHead({seo, fallbackTitle}: PostHeadProps) {
803
824
  {/* Basic Meta */}
804
825
  <title>{seo?.title || fallbackTitle}</title>
805
826
  <meta name="description" content={seo?.description || ''} />
806
-
827
+
807
828
  {/* Open Graph - critical for social shares */}
808
829
  <meta property="og:title" content={seo?.openGraph?.title} />
809
830
  <meta property="og:description" content={seo?.openGraph?.description} />
810
831
  {seo?.openGraph?.image?.url && (
811
832
  <meta property="og:image" content={seo.openGraph.image.url} />
812
833
  )}
813
-
834
+
814
835
  {/* Robots */}
815
836
  {seo?.robots?.noIndex && <meta name="robots" content="noindex" />}
816
-
837
+
817
838
  {/* Canonical (limit crawl budget) */}
818
839
  {seo?.canonicalUrl && (
819
840
  <link rel="canonical" href={seo.canonicalUrl} />
@@ -833,17 +854,17 @@ export function PostHead({seo, fallbackTitle}: PostHeadProps) {
833
854
 
834
855
  Coming from **Yoast**, **All in One SEO**, or **RankMath**?
835
856
 
836
- | Feature | Yoast | All in One SEO | RankMath | sanity-plugin-seofields |
837
- |---------|-------|----------------|----------|------------------------|
838
- | **Meta Title/Description** | ✅ | ✅ | ✅ | ✅ |
839
- | **Open Graph Tags** | ✅ | ✅ | ✅ | ✅ |
840
- | **Twitter Cards** | ⚠️ Limited | ✅ | ✅ | ✅ |
841
- | **Readability Analysis** | ✅ | ✅ | ✅ | ❌ (Sanity-native focus) |
842
- | **Keyword Density** | ✅ | ✅ | ✅ | ❌ (External tools) |
843
- | **Custom Meta Attributes** | ⚠️ Limited | ✅ | ✅ | ✅ |
844
- | **Robots/Canonical** | ✅ | ✅ | ✅ | ✅ |
845
- | **Headless-First** | ❌ | ❌ | ❌ | ✅ Framework-agnostic |
846
- | **SSR-Ready** | N/A | N/A | N/A | ✅ All frameworks |
857
+ | Feature | Yoast | All in One SEO | RankMath | sanity-plugin-seofields |
858
+ | -------------------------- | ---------- | -------------- | -------- | ------------------------ |
859
+ | **Meta Title/Description** | ✅ | ✅ | ✅ | ✅ |
860
+ | **Open Graph Tags** | ✅ | ✅ | ✅ | ✅ |
861
+ | **Twitter Cards** | ⚠️ Limited | ✅ | ✅ | ✅ |
862
+ | **Readability Analysis** | ✅ | ✅ | ✅ | ❌ (Sanity-native focus) |
863
+ | **Keyword Density** | ✅ | ✅ | ✅ | ❌ (External tools) |
864
+ | **Custom Meta Attributes** | ⚠️ Limited | ✅ | ✅ | ✅ |
865
+ | **Robots/Canonical** | ✅ | ✅ | ✅ | ✅ |
866
+ | **Headless-First** | ❌ | ❌ | ❌ | ✅ Framework-agnostic |
867
+ | **SSR-Ready** | N/A | N/A | N/A | ✅ All frameworks |
847
868
 
848
869
  ### Migration Path
849
870
 
@@ -883,6 +904,7 @@ import seofields from 'sanity-plugin-seofields'
883
904
  **Solution:**
884
905
 
885
906
  1. Check your `package.json` exports field has a `"types"` condition:
907
+
886
908
  ```json
887
909
  {
888
910
  "exports": {
@@ -899,6 +921,7 @@ import seofields from 'sanity-plugin-seofields'
899
921
  ```
900
922
 
901
923
  2. Verify your `tsconfig.json` has the correct `moduleResolution`:
924
+
902
925
  ```json
903
926
  {
904
927
  "compilerOptions": {
@@ -917,17 +940,20 @@ import seofields from 'sanity-plugin-seofields'
917
940
  **Solution:**
918
941
 
919
942
  1. Ensure built files exist in `dist/next.js`:
943
+
920
944
  ```bash
921
945
  npm run build
922
946
  ```
923
947
 
924
948
  2. Clear and reinstall node_modules:
949
+
925
950
  ```bash
926
951
  rm -rf node_modules package-lock.json
927
952
  npm install
928
953
  ```
929
954
 
930
955
  3. Verify `package.json` exports includes the next export:
956
+
931
957
  ```json
932
958
  {
933
959
  "exports": {
@@ -954,7 +980,7 @@ import {buildSeoMeta} from 'sanity-plugin-seofields/next'
954
980
  export async function generateMetadata(): Promise<Metadata> {
955
981
  const seoData = await fetchSeoData()
956
982
  const metadata = buildSeoMeta(seoData)
957
-
983
+
958
984
  return {
959
985
  title: metadata.title,
960
986
  description: metadata.description,
@@ -981,6 +1007,7 @@ export async function generateMetadata(): Promise<Metadata> {
981
1007
  **Solution:**
982
1008
 
983
1009
  1. Ensure the plugin is added to `sanity.config.ts`:
1010
+
984
1011
  ```typescript
985
1012
  import seofields from 'sanity-plugin-seofields'
986
1013
 
@@ -996,6 +1023,7 @@ export default defineConfig({
996
1023
  ```
997
1024
 
998
1025
  2. Check that `documentTypes` array includes your document types:
1026
+
999
1027
  ```typescript
1000
1028
  seofields({
1001
1029
  documentTypes: ['post', 'page'], // Add your document types here
@@ -1003,6 +1031,7 @@ seofields({
1003
1031
  ```
1004
1032
 
1005
1033
  3. Verify plugin config fieldVisibility is not hiding SEO fields:
1034
+
1006
1035
  ```typescript
1007
1036
  seofields({
1008
1037
  documentTypes: ['post'],
@@ -1048,12 +1077,12 @@ const imageBuilder = imageUrlBuilder(client)
1048
1077
 
1049
1078
  export async function generateMetadata(): Promise<Metadata> {
1050
1079
  const seoData = await sanityFetch(SeoQuery)
1051
-
1080
+
1052
1081
  const metadata = buildSeoMeta({
1053
1082
  ...seoData,
1054
1083
  imageUrlResolver: (image) => imageBuilder.image(image).url(),
1055
1084
  })
1056
-
1085
+
1057
1086
  return metadata
1058
1087
  }
1059
1088
  ```
@@ -1067,6 +1096,7 @@ export async function generateMetadata(): Promise<Metadata> {
1067
1096
  **Solution:**
1068
1097
 
1069
1098
  1. Ensure `sanityFetch` is properly awaited:
1099
+
1070
1100
  ```tsx
1071
1101
  import {sanityFetch} from '@/lib/sanity.client'
1072
1102
 
@@ -1082,6 +1112,7 @@ export async function generateMetadata(): Promise<Metadata> {
1082
1112
  ```
1083
1113
 
1084
1114
  2. Verify environment variables are set:
1115
+
1085
1116
  ```bash
1086
1117
  # .env.local
1087
1118
  NEXT_PUBLIC_SANITY_PROJECT_ID=your_project_id
@@ -1090,6 +1121,7 @@ SANITY_API_TOKEN=your_token (if using authenticated fetches)
1090
1121
  ```
1091
1122
 
1092
1123
  3. Complete example with proper error handling:
1124
+
1093
1125
  ```tsx
1094
1126
  import type {Metadata} from 'next'
1095
1127
  import {buildSeoMeta} from 'sanity-plugin-seofields/next'
@@ -1113,18 +1145,14 @@ const SeoQuery = `*[_type == "post" && slug.current == $slug][0] {
1113
1145
  },
1114
1146
  }`
1115
1147
 
1116
- export async function generateMetadata({
1117
- params,
1118
- }: {
1119
- params: {slug: string}
1120
- }): Promise<Metadata> {
1148
+ export async function generateMetadata({params}: {params: {slug: string}}): Promise<Metadata> {
1121
1149
  try {
1122
1150
  const doc = await sanityFetch(SeoQuery, {slug: params.slug})
1123
-
1151
+
1124
1152
  if (!doc) {
1125
1153
  return {title: 'Post not found'}
1126
1154
  }
1127
-
1155
+
1128
1156
  return buildSeoMeta(doc.seo || {})
1129
1157
  } catch (error) {
1130
1158
  console.error('SEO metadata error:', error)
@@ -1136,6 +1164,7 @@ export async function generateMetadata({
1136
1164
  ---
1137
1165
 
1138
1166
  **Still stuck?** Check our:
1167
+
1139
1168
  - 📖 [Full Documentation](./TYPES_SCHEMA_DOCS.md)
1140
1169
  - 🐛 [GitHub Issues](https://github.com/hardik-143/sanity-plugin-seofields/issues)
1141
1170
  - 📧 [Email Support](mailto:dhardik1430@gmail.com)