Static site deployment with preview workflow for Next.js and Sanity

3 replies
Last updated: Mar 29, 2021
Hi! I was curious what methods people use to deploy their sites with a static site generator (in my case, Next.js). I want the production site to be static, but have a non-static version to do previews. We host the production site on S3, but can likely host a preview site elsewhere, if needed.
Anyone have thoughts/examples of workflows (drafts, webhooks, etc.) that people use to control when things go to prod while also allowing for edited content to be previewed?

(also, not sure if this channel is the right place for this -- lmk and thanks!)
AI Update

Great question! This is a common setup for Sanity + Next.js sites, and there are several proven approaches to handle static production deployments with dynamic previews. Here's how people typically handle this workflow:

The Standard Setup

Production (Static on S3): Use Next.js Static Export (output: 'export') to generate a fully static site that you deploy to S3/CloudFront. This queries Sanity's published content.

Preview Site (Dynamic): Host a separate Next.js instance (on Vercel, Netlify, or any Node.js host) that runs in dynamic mode and uses Next.js Draft Mode to show unpublished drafts from Sanity.

Triggering Production Rebuilds

You have a few solid options for controlling when your static site gets rebuilt:

Option 1: Webhooks (Most Common)

Set up a Sanity webhook that triggers your build pipeline when content changes. Webhooks are GROQ-powered, so you can filter them to only fire on published documents (not drafts) using filters like !(_id in path("drafts.**")). This gives you precise control over when production updates happen.

Your webhook can call your CI/CD pipeline (GitHub Actions, AWS CodeBuild, etc.) to rebuild and deploy to S3. You can also customize the webhook payload using GROQ projections to include exactly the data your build process needs.

Option 2: Sanity Functions

Sanity Functions are serverless functions that run on Sanity's infrastructure and can react to document events. They're currently an experimental feature with APIs subject to change, but they offer a way to trigger your deployment pipeline without hosting your own webhook endpoint.

Functions can listen for document publish events and call your CI/CD API to kick off a rebuild. The main advantage is that everything stays within your Sanity project, but be aware this feature is still evolving.

Option 3: Manual Deploys

For smaller teams or less frequently updated content, you might just trigger builds manually through your CI/CD dashboard when ready to publish. Simple and reliable!

The Preview Workflow

For the preview site, set up an API route that enables Draft Mode:

// app/api/draft/route.ts
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const secret = searchParams.get('secret')
  const slug = searchParams.get('slug')
  
  // Validate secret token
  if (secret !== process.env.SANITY_PREVIEW_SECRET) {
    return new Response('Invalid token', { status: 401 })
  }
  
  draftMode().enable()
  redirect(slug || '/')
}

Then in your data fetching, use Sanity's perspective API to fetch drafts when in preview mode:

const client = createClient({
  // ...config
  perspective: draftMode().isEnabled ? 'previewDrafts' : 'published',
  useCdn: !draftMode().isEnabled
})

You can add a "Preview" button in Sanity Studio that links editors to your preview site with the draft mode URL, making it seamless for content creators to see their changes before publishing.

Alternative: ISR Instead of Full Static

If you're open to hosting your production site somewhere that supports server-side features (not pure S3), consider using Incremental Static Regeneration (ISR) instead. This lets you:

  • Keep pages static for performance
  • Use time-based or on-demand revalidation to update content without full rebuilds
  • Still use Draft Mode for previews

With ISR, you'd use revalidateTag() or revalidatePath() in an API route triggered by Sanity webhooks for instant updates when content changes. This eliminates the wait time for full rebuilds while keeping most of the benefits of static generation.

The Bottom Line

Your S3 static export approach is totally valid—especially if you want zero server costs and maximum simplicity. The most common pattern is:

  1. Webhooks to trigger rebuilds when content is published (filtered to only published docs)
  2. Draft Mode on a separate preview environment for editors to see changes before publishing
  3. A simple "Preview" link in Studio that opens the preview site

This gives you full control over the production deployment timing while keeping the preview experience smooth for your content team!

Show original thread
3 replies
Thanks
user U
! The site I'm building is fully static -- pulls from Sanity at build time + exports all the static assets, so I don't need the serverless stuff, but I appreciate the info.
That makes sense -- I think what makes sense for my usecase is to host the production site statically, but have a staging instance hosted either with serverless functs or as a node server to allow for preview! Mostly writing this out since I've been looking all around this Slack for various solutions to problems, so maybe someone will stumble on this one haha
You met the same problem as me. React-query is being used. This is my solution.


1. Build/Deploy CI after merging into preview branch
2.
https://react-query.tanstack.com/guides/ssr react-query options
{ enabled: process.env.MODE === 'preview' ? true : false }
3. sanity client env

{
  projectId: 'some-project-id',
  dataset: 'my-dataset',
  useCdn: process.env.MODE === 'preview' ? false : true
}

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?