Everything *[NYC] 2026: see what we announced. →
Skip to content

Why doesn't my published Sanity document show on my site when GROQ returns it?

0repliesLast updated: Nov 6, 2026

I published a document in Sanity Studio, and my GROQ query returns the new content in Vision, but my website still shows the old version or doesn't show the document at all. Why is the frontend different from Vision, and how do I make published changes appear on the site?
AI Update

Vision and your website don't send the same request. Vision runs authenticated queries against the live API, using whatever dataset, API version and perspective you picked in its dropdowns. Your site probably uses a different client configuration, may read through the API CDN, and then caches the result in your framework (a static build, ISR or the Next.js data cache). To fix it, find the layer that's serving old data and invalidate it when you publish.

1. Perspective and token: is the site asking for the same documents?

A perspective decides which document versions a query can see (Perspectives):

PerspectiveReturns
publishedPublished documents only, with no drafts. This is the default from API version 2025-02-19.
draftsDrafts treated as published, including drafts that were never published
rawDrafts, versions and published documents side by side. This was the default before 2025-02-19.

Vision uses your logged-in Studio credentials. A frontend client usually makes unauthenticated requests, and those only return publicly queryable documents. That's why drafts can show up in Vision but not on your site (Vision lesson). Unauthenticated requests never return drafts or release versions, even from a public dataset (Vision plugin). If the document only appears in Vision with a drafts or raw perspective, it hasn't been published. Also compare the dataset and API version in Vision's dropdowns with your client config.

2. API CDN or live API

Static builds should use the live API. You can override the setting for a single request with .withConfig({useCdn: false}) (caching course). Queries that use the drafts perspective are never cached in the CDN, so set useCdn: false explicitly for those.

3. Framework caching

The Sanity CDN and the Next.js data cache are two separate caches, and stale content can come from either one or both. On hosts like Vercel, the Next.js cache survives redeploys (Next.js caching guide). A common helper:

export async function sanityFetch<const QueryString extends string>({
  query, params = {}, revalidate = 60, tags = [],
}: { query: QueryString; params?: QueryParams; revalidate?: number | false; tags?: string[] }) {
  return client.fetch(query, params, {
    next: { revalidate: tags.length ? false : revalidate, tags },
  })
}

When you pass tags, the query stays cached until revalidateTag() is called. Tags and time-based revalidation are mutually exclusive.

4. Invalidate on publish

Webhook plus a revalidate route. Create a GROQ-powered webhook with Create, Update and Delete events, a filter such as _type in ["post", "author"], the projection {_type}, POST as the method, and a secret (webhook guide):

// src/app/api/revalidate-tag/route.ts
import { revalidateTag } from 'next/cache'
import { NextResponse, type NextRequest } from 'next/server'
import { parseBody } from 'next-sanity/webhook'

export async function POST(req: NextRequest) {
  const secret = process.env.SANITY_REVALIDATE_SECRET
  if (!secret) return new Response('Missing environment variable SANITY_REVALIDATE_SECRET', { status: 500 })
  // true = wait for the Content Lake/CDN to catch up before revalidating
  const { isValidSignature, body } = await parseBody<{ _type: string }>(req, secret, true)
  if (!isValidSignature) return new Response('Invalid signature', { status: 401 })
  if (!body?._type) return new Response('Bad Request', { status: 400 })
  revalidateTag(body._type)
  return NextResponse.json({ revalidated: body._type })
}

Tag your queries to match, for example sanityFetch({ query: POSTS_QUERY, tags: ['post', 'author'] }). For path-based revalidation, project {"path": select(...)} and call revalidatePath(body.path) instead. In the Pages Router, call res.revalidate(path), because revalidateTag isn't supported there.

Pass true as the third argument to parseBody. Webhooks fire before the CDN updates, so without the delay your revalidation can refill the cache with stale data.

Live Content API (Sanity's default recommendation). In the App Router with next-sanity v13+, use defineLive from next-sanity/live, fetch data with its sanityFetch, and render <SanityLive /> in the root layout (live content guide). Don't mix it with the manual sanityFetch helper above. Your frontend's origin must be in the project's CORS origins. By default, updates are stale-while-revalidate, so a visitor may need to refresh to see a change.

Checklist

Was this answer helpful?

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Related contributions