Query for Sanity preview mode in Next.js not returning draft posts

14 replies
Last updated: May 31, 2023
Hello everyone – hoping someone might be able to help me.
I've recently setup Sanity preview mode with Next.js 13 (app directory). The query I'm using the fetch my post in preview mode looks like this:


*[_type == "post" && slug.current == $slug][0]{ ..., authors[]->, categories[]->]
This however, doesn't bring back any posts that are in draft mode, only posts that are published are returned, which seems to defeat the purpose. I couldn't find anything in the docs that explains how to include draft posts in the response, so I'm wondering if I'm going about this in the right way. Should it be the case, instead, that posts have a field called, say 'publishedAt', which is used to filter out posts on the main site that haven't yet been published?

Any pointers would be greatly appreciated.
AI Update

You're on the right track! The issue is that you need to configure your Sanity client to use the correct perspective to see draft documents. By default, Sanity clients use the published perspective (especially in newer API versions), which only returns published documents.

To see drafts in your preview mode, you need to set the perspective to previewDrafts when creating your Sanity client. This perspective will prioritize draft versions over published ones when both exist, which is exactly what you want for preview functionality.

Here's how to set it up with Next.js 13 app router:

import { createClient } from 'next-sanity'

// Client for preview mode
export const previewClient = createClient({
  projectId: 'your-project-id',
  dataset: 'your-dataset',
  apiVersion: '2024-01-01',
  useCdn: false, // Important: disable CDN for preview
  perspective: 'previewDrafts', // This is the key setting
  token: process.env.SANITY_API_READ_TOKEN, // Required for drafts
})

// Regular client for production
export const client = createClient({
  projectId: 'your-project-id',
  dataset: 'your-dataset',
  apiVersion: '2024-01-01',
  useCdn: true,
  perspective: 'published',
})

Important notes:

  1. Token required: To access draft documents, you need to provide an API token with read permissions. Draft content is private and requires authentication.

  2. Disable CDN: Set useCdn: false for preview clients since draft content shouldn't be cached.

  3. Perspective options: As explained in the Sanity perspectives documentation, there are several perspectives:

    • published - Only published documents
    • previewDrafts - Prioritizes drafts over published (perfect for preview)
    • raw - Returns both drafts and published (can cause duplicates)

Your GROQ query itself is fine - you don't need to modify it. The perspective setting handles filtering draft vs published documents at the client level.

When implementing Draft Mode with Next.js, make sure to use the preview client (with previewDrafts perspective) when Draft Mode is active, and the regular client (with published perspective) for normal page views.

To answer your other question: while having a publishedAt field can be useful for scheduling or filtering, it's not necessary for basic draft/published workflows. Sanity's built-in draft system (where drafts are prefixed with drafts. in their _id) combined with perspectives is the standard approach for handling unpublished content.

are you trying to preview within the Sanity studio? if not, you need to make authenticated API requests:
Drafts are, by default, non-public and only visible to authenticated requests with the right permissions. This means that to preview drafts outside of the Studio context; you will have to make authenticated requests to the Sanity API to fetch the draft document. As a consequence, you need to make sure that you can access the draft document from your front end.
https://www.sanity.io/docs/preview-content-on-site
user V
thank you for this!
Yes, that is correct – I'm trying to preview within Sanity Studio, using the Iframe Pane plugin.
I'm using the
definePreview
hook that's provided by
next-sanity
to fetch the data (as per their guide), in the following way:
export const usePreview = definePreview({
  projectId: config.projectId,
  dataset: config.dataset,
  onPublicAccessOnly,
});
export const PreviewPost = ({ slug }: { slug: string }) => {
  const data = usePreview(null, getPostBySlugQuery, {
    slug,
  });

  return (
    <>
      <Post post={data} />
      <Link
        className="fixed bottom-0 right-0 w-full p-6 font-bold text-white bg-blue-500"
        href={getRoute({ path: pathNames.API_POST_DRAFT_EXIT })}
      >
        Exit Preview
      </Link>
    </>
  );
};
And this is the query I'm using (what is referenced above as
getPostBySlugQuery
)
export const getPostBySlugQuery = groq`*[_type == "post" && slug.current == $slug][0]{ ..., authors[]->, categories[]->]`
From what I gather, the authentication piece is taken care of by this library:
So I guess based on the above, I would expect draft posts to be returned, if the only condition for them being returned is that the user needs to be authenticated.
However, the article you shared also mentions the following:
So I'm wondering if I need to modify my query to look like the third bullet point – I'm going to try that out now to see if that works, will report back, but also curious to hear your thoughts as to whether this is the right approach. Thank you in advance!
user V
– actually, I've been digging around, and I think you're right – the issue is that I'm not setting an authentication cookie at any point, so when the request is made from the frontend, it fails (as you'd expect).
Thanks for your help, I'm going to keep playing around it and see if I can get it working.

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.

Was this answer helpful?