Sanity Troubleshooting | Blue Frog Docs

Sanity Troubleshooting

Common issues and solutions for Sanity-powered websites including performance, tracking, and integration problems.

Sanity Troubleshooting

This guide covers common issues specific to Sanity-powered websites. Since Sanity is a headless CMS, most issues relate to your frontend framework implementation.

Common Issues Overview

Sanity is a headless CMS with real-time collaboration and flexible content structures. Troubleshooting typically involves GROQ queries, content schema, frontend integration, preview mode, and real-time updates.

Common Issue Categories

Performance Issues

  • GROQ query optimization
  • Image loading with Sanity's CDN
  • Real-time preview performance
  • Frontend framework SSR/SSG issues

Tracking Issues

  • Analytics not firing in preview mode
  • Events missing content metadata
  • Cross-domain tracking with Sanity Studio
  • Real-time content update tracking

Installation Problems

Sanity CLI Setup

Installation Failures

Install Sanity CLI:

# Global install
npm install -g @sanity/cli

# Or use npx
npx @sanity/cli --version

Common errors:

# Permission denied
sudo npm install -g @sanity/cli

# Or use nvm for local install
nvm use 18
npm install -g @sanity/cli

Project Creation Issues

Create new project:

npm create sanity@latest

# Or with CLI
sanity init

If project creation fails:

# Check Node version (needs 14+)
node -v

# Clear npm cache
npm cache clean --force

# Try again
npm create sanity@latest

Client SDK Installation

Install client libraries:

# For React/Next.js
npm install @sanity/client @sanity/image-url next-sanity

# For Vue/Nuxt
npm install @sanity/client @sanity/image-url

# TypeScript definitions
npm install -D @sanity/types

Verify installation:

import {createClient} from '@sanity/client'

const client = createClient({
  projectId: 'your-project-id',
  dataset: 'production',
  apiVersion: '2024-01-01',
  useCdn: true
})

// Test connection
client.fetch('*[_type == "post"][0..2]')
  .then(console.log)
  .catch(console.error)

Analytics Integration

Next.js Pages Router:

// pages/_app.js
import Script from 'next/script'

export default function App({ Component, pageProps }) {
  // Don't track in preview mode
  const isPreview = pageProps.preview || false

  return (
    <>
      {!isPreview && (
        <>
          <Script
            strategy="afterInteractive"
            src={`https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX`}
          />
          <Script id="google-analytics" strategy="afterInteractive">
            {`
              window.dataLayer = window.dataLayer || [];
              function gtag(){dataLayer.push(arguments);}
              gtag('js', new Date());
              gtag('config', 'G-XXXXXXXXXX');
            `}
          </Script>
        </>
      )}
      <Component {...pageProps} />
    </>
  )
}

Next.js App Router:

// app/layout.js
import { draftMode } from 'next/headers'
import Analytics from './analytics'

export default function RootLayout({ children }) {
  const { isEnabled } = draftMode()

  return (
    <html lang="en">
      <body>
        {!isEnabled && <Analytics />}
        {children}
      </body>
    </html>
  )
}

Configuration Issues

Issue Symptoms Common Causes Solutions
Preview Mode Analytics Tracking in preview Preview detection not implemented Check draftMode and disable analytics
GROQ Query Errors Invalid query syntax Wrong GROQ syntax Validate query in Vision plugin
Image Not Loading Broken images Wrong image URL builder Use @sanity/image-url
CORS Errors API blocked CORS not configured Add domain to CORS settings
Schema Validation Failing Document won't save Required fields missing Check schema validation rules
Real-Time Not Working Updates not appearing Listener not configured Set up event listener correctly
Token Authentication 401 Unauthorized Missing/invalid token Check API token in env variables
CDN Stale Content Old content served CDN caching Use useCdn: false or purge cache
References Not Resolving Missing referenced data References not expanded Add references to GROQ projection
Webhook Not Triggering Deployment not triggered Webhook misconfigured Verify webhook URL and secret

Debugging with Developer Tools

Sanity Studio Debugging

Vision Plugin (GROQ Playground)

Install Vision:

npm install @sanity/vision

Add to sanity.config.js:

import {defineConfig} from 'sanity'
import {visionTool} from '@sanity/vision'

export default defineConfig({
  name: 'default',
  title: 'My Project',
  projectId: 'your-project-id',
  dataset: 'production',

  plugins: [visionTool()]
})

Test GROQ queries:

  1. Open Studio
  2. Click Vision tab
  3. Write query:
*[_type == "post"] {
  _id,
  title,
  slug,
  author-> {
    name,
    image
  }
}
  1. Click "Fetch" to see results

Schema Validation

Test schema in Studio:

// schemas/post.js
export default {
  name: 'post',
  title: 'Post',
  type: 'document',
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: 'string',
      validation: Rule => Rule.required().min(10).max(80)
    },
    {
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96,
      },
      validation: Rule => Rule.required()
    }
  ],
  preview: {
    select: {
      title: 'title',
      subtitle: 'slug.current'
    }
  }
}

Check for errors:

  • Open Studio
  • Try creating/editing document
  • Look for validation messages

Frontend Debugging

GROQ Query Logging

Log queries and performance:

import {createClient} from '@sanity/client'

const client = createClient({
  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
  apiVersion: '2024-01-01',
  useCdn: true,
})

// Wrapper with logging
async function fetchWithLog(query, params = {}) {
  console.log('GROQ Query:', query)
  console.log('Parameters:', params)

  const start = performance.now()
  try {
    const result = await client.fetch(query, params)
    const duration = performance.now() - start
    console.log(`Query took ${duration.toFixed(2)}ms`)
    console.log('Result:', result)
    return result
  } catch (error) {
    console.error('Query failed:', error)
    throw error
  }
}

// Usage
const posts = await fetchWithLog(`*[_type == "post"][0...10]`)

Network Tab Inspection

Monitor Sanity API calls:

  1. Open DevTools → Network
  2. Filter: api.sanity.io or cdn.sanity.io
  3. Look for:
    • /v1/data/query/ - GROQ queries
    • /v1/data/listen - Real-time updates
    • /images/ - Image CDN

Check request:

https://projectid.api.sanity.io/v1/data/query/production?query=*[_type=="post"]

Headers:
  Authorization: Bearer <token>

Response:
  { "result": [...], "ms": 42 }

Browser Console Debugging

Make client available:

if (typeof window !== 'undefined') {
  window.sanityClient = client
  console.log('Sanity client available at window.sanityClient')
}

// Test in console:
// const posts = await window.sanityClient.fetch('*[_type == "post"][0..2]')

Platform-Specific Challenges

Preview Mode Tracking

Disable Analytics in Preview

Next.js Pages Router:

// lib/sanity.js
export async function getClient(preview) {
  return createClient({
    projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
    dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
    apiVersion: '2024-01-01',
    useCdn: !preview, // Don't use CDN in preview
    token: preview ? process.env.SANITY_API_TOKEN : undefined
  })
}

// pages/posts/[slug].js
export async function getStaticProps({ params, preview = false }) {
  const client = getClient(preview)
  const post = await client.fetch(query, { slug: params.slug })

  return {
    props: {
      post,
      preview
    }
  }
}

// pages/_app.js
export default function App({ Component, pageProps }) {
  const isPreview = pageProps.preview || false

  useEffect(() => {
    if (isPreview) {
      console.log('Preview mode - analytics disabled')
      return
    }

    // Initialize analytics
    if (typeof gtag !== 'undefined') {
      gtag('config', 'G-XXXXXXXXXX')
    }
  }, [isPreview])

  return <Component {...pageProps} />
}

App Router:

// app/layout.js
import { draftMode } from 'next/headers'

export default function RootLayout({ children }) {
  const { isEnabled } = draftMode()

  return (
    <html>
      <body>
        {!isEnabled && <Analytics />}
        {children}
        {isEnabled && <PreviewBanner />}
      </body>
    </html>
  )
}

Real-Time Updates

Track Content Changes

Listen to real-time updates:

import {createClient} from '@sanity/client'

const client = createClient({
  projectId: 'your-project-id',
  dataset: 'production',
  apiVersion: '2024-01-01',
  useCdn: false, // Must be false for real-time
  token: process.env.SANITY_API_TOKEN // Required for listen
})

// Listen to post changes
const query = '*[_type == "post"]'

const subscription = client.listen(query).subscribe({
  next: (update) => {
    console.log('Real-time update:', update)

    if (typeof gtag !== 'undefined') {
      gtag('event', 'content_update', {
        'content_type': update.result?._type,
        'content_id': update.documentId,
        'transition': update.transition // 'update', 'appear', 'disappear'
      })
    }
  },
  error: (err) => console.error('Listen error:', err)
})

// Cleanup
// subscription.unsubscribe()

GROQ Query Optimization

Tracking Query Performance

Optimize complex queries:

// Bad - fetches everything
const posts = await client.fetch(`
  *[_type == "post"] {
    ...,
    author->,
    categories[]->
  }
`)

// Good - specific fields only
const posts = await client.fetch(`
  *[_type == "post"] {
    _id,
    title,
    slug,
    publishedAt,
    "authorName": author->name,
    "categoryNames": categories[]->title
  }[0...10]
`)

// Even better - with pagination
const posts = await client.fetch(`
  *[_type == "post"] | order(publishedAt desc) [0...10] {
    _id,
    title,
    slug,
    publishedAt,
    "authorName": author->name,
    "excerpt": pt::text(body)[0...200]
  }
`)

Track query performance:

async function trackQuery(queryName, queryFn) {
  const start = performance.now()

  try {
    const result = await queryFn()
    const duration = performance.now() - start

    if (typeof gtag !== 'undefined') {
      gtag('event', 'query_performance', {
        'query_name': queryName,
        'duration_ms': Math.round(duration),
        'result_count': result?.length || 0
      })
    }

    return result
  } catch (error) {
    if (typeof gtag !== 'undefined') {
      gtag('event', 'query_error', {
        'query_name': queryName,
        'error_message': error.message
      })
    }
    throw error
  }
}

// Usage
const posts = await trackQuery('fetch_posts', () =>
  client.fetch(`*[_type == "post"][0...10]`)
)

Content Lake Tracking

Track Document Views

Track specific content:

async function trackContentView(documentType, documentId) {
  try {
    // Fetch document metadata
    const doc = await client.fetch(
      `*[_type == $type && _id == $id][0] {
        _id,
        _type,
        title,
        publishedAt
      }`,
      { type: documentType, id: documentId }
    )

    if (typeof gtag !== 'undefined') {
      gtag('event', 'content_view', {
        'content_type': doc._type,
        'content_id': doc._id,
        'content_title': doc.title,
        'published_date': doc.publishedAt
      })
    }
  } catch (error) {
    console.error('Content tracking failed:', error)
  }
}

// Usage in component
useEffect(() => {
  trackContentView('post', postId)
}, [postId])

Error Messages and Solutions

Common Sanity Errors

"Unable to connect to Sanity API"

Error:

Could not connect to Sanity API

Check configuration:

// Verify environment variables
console.log('Project ID:', process.env.NEXT_PUBLIC_SANITY_PROJECT_ID)
console.log('Dataset:', process.env.NEXT_PUBLIC_SANITY_DATASET)

// Test connection
const client = createClient({
  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
  apiVersion: '2024-01-01',
  useCdn: true
})

client.fetch('*[_type == "post"][0]')
  .then(() => console.log('✓ Connected'))
  .catch(err => console.error('✗ Connection failed:', err))

"Invalid GROQ query"

Error:

{"error":"Invalid query","message":"Query is invalid"}

Test query in Vision:

  1. Open Sanity Studio
  2. Go to Vision tab
  3. Paste query
  4. Fix syntax errors

Common mistakes:

# Wrong - missing quotes
*[_type == post]

# Correct
*[_type == "post"]

# Wrong - incorrect reference syntax
*[_type == "post"]{author}

# Correct
*[_type == "post"]{author->}

"Document validation failed"

Error:

Document failed validation: "title" is required

Check schema:

// Ensure required fields are provided
{
  name: 'title',
  type: 'string',
  validation: Rule => Rule.required()
}

Handle in frontend:

// Defensive querying
const posts = await client.fetch(`
  *[_type == "post" && defined(title) && defined(slug)]
`)

Performance Problems

Slow GROQ Queries

Problem: Queries taking too long

Solutions:

  1. Use projections:
*[_type == "post"] {
  _id,
  title,
  slug
}
  1. Limit results:
*[_type == "post"][0...10]
  1. Filter efficiently:
*[_type == "post" && publishedAt < now()][0...10]
  1. Avoid deep references:
# Instead of multiple levels
author->company->address->city

# Fetch directly
"companyCity": author->company->address->city

Image Optimization

Use Sanity's image pipeline:

import imageUrlBuilder from '@sanity/image-url'

const builder = imageUrlBuilder(client)

function urlFor(source) {
  return builder.image(source)
}

// Usage
<img
  src={urlFor(post.mainImage)
    .width(800)
    .height(600)
    .fit('crop')
    .auto('format')
    .url()}
  alt={post.mainImage.alt}
  loading="lazy"
/>

Next.js Image component:

import Image from 'next/image'
import {useNextSanityImage} from 'next-sanity-image'

function SanityImage({ asset }) {
  const imageProps = useNextSanityImage(client, asset)

  return (
    <Image
      {...imageProps}
      layout="responsive"
      sizes="(max-width: 800px) 100vw, 800px"
    />
  )
}

When to Contact Support

Sanity Support

Contact when:

  • API issues or downtime
  • Billing questions
  • Security concerns
  • Feature requests

Support channels:

When to Hire a Developer

Complex scenarios:

  1. Custom Studio plugins
  2. Advanced schema architectures
  3. Real-time collaboration features
  4. Custom input components
  5. Complex GROQ queries
  6. Performance optimization at scale
  7. Migration from other CMS
  8. Custom deployment workflows

Advanced Troubleshooting

Development Environment Setup

Complete .env.local:

# Required
NEXT_PUBLIC_SANITY_PROJECT_ID="your-project-id"
NEXT_PUBLIC_SANITY_DATASET="production"

# For authenticated requests
SANITY_API_TOKEN="your-token"

# For preview mode
SANITY_PREVIEW_SECRET="your-secret"

# For webhooks
SANITY_WEBHOOK_SECRET="your-webhook-secret"

Generate types:

# Install schema codegen
npm install -D sanity-codegen

# Generate types
npx sanity-codegen

# Use in TypeScript
import type {Post} from '../generated/schema'

const post: Post = await client.fetch(...)

Webhook Debugging

Test webhook:

// pages/api/revalidate.js
export default async function handler(req, res) {
  // Validate secret
  if (req.body.secret !== process.env.SANITY_WEBHOOK_SECRET) {
    return res.status(401).json({ message: 'Invalid secret' })
  }

  console.log('Webhook received:', {
    type: req.body._type,
    id: req.body._id
  })

  try {
    // Revalidate specific path
    await res.revalidate(`/posts/${req.body.slug.current}`)

    // Track webhook
    if (typeof gtag !== 'undefined') {
      gtag('event', 'webhook_revalidate', {
        'content_type': req.body._type,
        'content_id': req.body._id
      })
    }

    return res.json({ revalidated: true })
  } catch (err) {
    return res.status(500).send('Error revalidating')
  }
}

For platform-agnostic troubleshooting, see:

// SYS.FOOTER