Live-as-you-type previewing of draft content is the ultimate upgrade for your content creators so that they can see changes in real-time and gain increased confidence to hit that publish button.
Composable content management is a powerful tool. Content separated from its presentation is the right way to think about content modelling. However, confidence can suffer when the edited content lacks the context of how those edits will affect real-world outputs.
This guide is deliberately focused on the experience of manually creating a new Next.js 13 application along with a separate Sanity Studio project and writing the code to bring the two together.
All the instructions below could also be adapted to an existing Next.js application:
Want working code faster?Our Next.js starter has an example blog schema and live preview already set up and can be instantly deployed to Vercel.
Interested in using Next.js’ app directory? This guide will only cover the pages directory approach. Fortunately, though the app directory strategy is still in beta, next-sanity already supports it! You can find instructions on configuring next-sanity with the app directory in that package’s readme.
TypeScript is not required. The code examples in this guide are all in TypeScript; However, TypeScript is not necessary for any of this to work. You need to remove the types from these examples if working with JavaScript.
Assumptions
You already have a Sanity account
You have some familiarity with both Sanity Studio and Next.js
You are reasonably confident with JavaScript in general and React in particular.
Create a new Next.js 13 project
Create a new project using the command below. The code examples in this project will assume you’ve selected TypeScript and eslint support – but they’re not necessary to continue.
# from the command line
npx create-next-app@latest
# give your project a name
What is your project named? nextjs-live-preview
# enter the new project's directorycd nextjs-live-preview
# run the development servernpm run dev
At the time of writing, this will configure a Next.js project using the pages directory.
Visit http://localhost:3000 in your web browser, and you should see this landing screen to show it’s been installed correctly.
The default home page of a new Next.js 13 project
The new Next.js website comes with some code boilerplate. So that you can more easily see what’s Sanity and what’s Next.js – you will remove almost all of it.
First, let’s simplify the home page route file and update it with the code below.
// ./pages/index.tsxexportdefaultfunctionHome(){return(<main className="flex items-center justify-center min-h-screen">
Populate me with Sanity Content
</main>)}
Secondly, to simplify styling, you'll add Tailwind CSS’s Play CDN to the entire App. Update the _app.tsx file with the below.
Now our Next.js app at http://localhost:3000 should look much simpler:
A blank webpage other than the words "Populate me with Sanity Content"
Create a new Sanity Studio v3 project
You‘ll create a new Sanity Studio in its dedicated folder for this tutorial. Using the “Movies” template and dataset so that you have some data to start with.
# from the command linenpm create sanity@latest -- --template moviedb --create-project "Sanity Live Preview" --dataset production
# follow prompts during install# this tutorial uses TypeScript# and choose to import the sample data# enter the new project's directorycd sanity-live-preview
# run the development servernpm run dev
For complete instructions and troubleshooting, see “Create a Sanity project” in the Documentation.
Pre-flight check
You should now have two folders:
/nextjs-live-preview -> contains our Next.js 13 site
/sanity-live-preview -> contains our Sanity Studio v3
From which you can run both projects separately
http://localhost:3000 to view the Next.js website
http://localhost:3333 to view the Sanity Studio
Linking Sanity to Next.js with next-sanity
The next-sanity toolkit and associated packages include helpers to query and display Sanity content. You’ll start by using the install script provided in the repository.
# in /nextjs-live-previewnpminstall next-sanity @portabletext/react @sanity/image-url
This will install:
next-sanity The toolkit with helpers for previewing content and embedding the Sanity Studio within the Next.js App
@portabletext/react A React component for rendering the contents of a portable text field
@sanity/image-url A helper library to generate complete image URLs from just the Project ID, Dataset, and ID of a Sanity image asset.
You'll now need to create some new files within the project.
Set up an environment variables file to read project details from. You can find these values from sanity.config.ts inside your new Sanity Studio project or by logging into sanity.io/manage
Note: You’ll need to recreate these environment variables when deploying the site to your hosting.
Fetching data from Sanity
Before you set up Live Preview to see draft content from Sanity, you should confirm you can fetch published data.
Update the index route to include a getStaticProps function. In it, you’ll import the Sanity Client to query for every movie document that has a valid slug.
// ./pages/index.tsximport{ groq }from"next-sanity";importtype{ SanityDocument }from"@sanity/client";import{ client }from"../lib/sanity.client";import Movies from"../components/Movies";const query = groq`*[_type == "movie" && defined(slug.current)]{
_id,
title,
slug
}`;exportconstgetStaticProps=async()=>{const data =await client.fetch(query);return{ props:{ data }};};exportdefaultfunctionHome({ data }:{ data: SanityDocument[]}){return<Movies movies={data}/>}
Create a new Component file to display the list of movie data:
// ./components/Movies.tsximport Link from"next/link";importtype{ SanityDocument }from"@sanity/client";import Head from"next/head";exportdefaultfunctionMovies({ movies }:{ movies: SanityDocument[]}){return(<><Head><title>{movies.length} Movies</title></Head><main className="container mx-auto grid grid-cols-1 divide-y divide-blue-100">{movies.map((movie)=>(<Link key={movie._id} href={movie.slug.current} className="p-4 hover:bg-blue-50"><h2>{movie.title}</h2></Link>))}</main></>);}
Your home page at http://localhost:3000 should now look like this:
Content queried from a Sanity dataset and displayed in a Next.js application
If so, great … until you click on one of those links.
You can fix that 404 by adding another route. Create a new file in ./pages
You’ll likely also need to make an update to next.config.ts so that next/image will load images from the Sanity CDN:
images:{
domains:['cdn.sanity.io'],}
You should now be able to click links on the home page and see pages just like these:
Our Next.js application can now display a Sanity image and block content!
Notice how this route uses the <PortableText /> component to render block content styled by Tailwind CSS’s Typography prose class names.
Status update
You should now have the following:
A working Sanity Studio with placeholder content
A Next.js application with a home page that lists published pages of content – and those individual pages show rich text and an image.
Now let’s see changes made in Sanity Studio on the Next.js website!
Setup next-sanity’s preview functionality
Add a CORS origin for our project
Any application that needs to make authenticated requests to our Sanity Project must have its URL added as a valid CORS origin.
This can be done inside sanity.io/manage; navigate to the API tab and enter http://localhost:3000, the default URL for developing a local Next.js project.
Ensure you check ”Allow credentials.”
Note: You must do this again when you deploy your application to a hosting provider, adding its URL to the list of CORS origins.
If you see the error “Failed to fetch” from your front end while setting up Live Preview, likely, this step wasn’t performed.
You'll need to add a new CORS origin for every URL your Next.js application is deployed to where you'd like to use live preview
Add more routes to the Next.js application
Following the instructions from the next-sanity README, you'll need to create new routes so that users can enter and exit preview mode – which will display draft content.
The next-sanity readme details trade-offs on authenticating users into preview mode. This tutorial relies on the user being logged into the same project’s Sanity Studio. So you won't need to add additional authentication logic or a token to this project.
A usePreview() custom hook for fetching preview data when authenticated:
// ./src/lib/sanity.preview.tsimport{definePreview}from'next-sanity/preview'import{projectId, dataset}from'./sanity.client'functiononPublicAccessOnly(){thrownewError(`Unable to load preview as you're not logged in`)}exportconst usePreview =definePreview({projectId, dataset, onPublicAccessOnly})
How this will work:
Any visitor that navigates to localhost:3000/api/preview will be redirected to the home page, with the site put into “Preview Mode”
Preview Mode is a preview true/false value available in getStaticProps and passed down to the document.
If that user is authenticated into the Sanity Studio, they’ll see draft (unpublished) content and see the website update as changes are made to documents.
With preview mode active, server-side queries are skipped and only made client-side by the usePreview() hook.
We’ll need to update our home page route (yes, again) to include:
A few new imports
Account for preview mode potentially being activated
A component that will perform an authenticated query client-side with usePreview()
// ./pages/index.tsximport{ lazy }from"react";import{ groq }from"next-sanity";importtype{ SanityDocument }from"@sanity/client";import{ client }from"../lib/sanity.client";import Movies from"../components/Movies";import{ PreviewSuspense }from"next-sanity/preview";const PreviewMovies =lazy(()=>import("../components/PreviewMovies"));const query = groq`*[_type == "movie" && defined(slug.current)]{
_id,
title,
slug
}`;exportconstgetStaticProps=async({ preview =false})=>{if(preview){return{ props:{ preview }};}const data =await client.fetch(query);return{ props:{ preview, data }};};exportdefaultfunctionHome({
preview,
data,}:{
preview: Boolean;
data: SanityDocument[];}){// PreviewSuspense shows while data is being fetched// The fetch happens inside PreviewMoviesreturn preview ?(<PreviewSuspense fallback="Loading..."><PreviewMovies query={query}/></PreviewSuspense>):(<Movies movies={data}/>);}
Create a new component for the authenticated query:
// ./components/PreviewMovies.tsximport Link from"next/link";import{ usePreview }from"../lib/sanity.preview";import Movies from"./Movies";exportdefaultfunctionPreviewMovies({ query }:{ query:string}){const data =usePreview(null, query);return(<><Movies movies={data}/><Link
className="bg-blue-500 p-6 text-white font-bold fixed bottom-0 right-0"
href="/api/exit-preview">
Exit Preview
</Link></>);}
Now when you visit the Next.js website at http://localhost:3000 you should still see the same list of movie pages.
However, if you visit http://localhost:3000/api/preview you’ll be redirected to the home page with preview mode activated. You’ll see the “Exit Preview” button in the bottom right.
Bring up your Sanity Studio in another browser and begin to make changes – or even create new documents – and see the list update as you type!
With Next.js in Preview Mode, we're now seeing new draft documents listed
However, if you click on any movie page links, those pages are not showing updates. We’ll need to update our [slug].tsx file similarly.
Like our home page, we’ll need a wrapper for the Movie component to perform the client-side query.
// ./components/PreviewMovie.tsximport Link from"next/link";import{ usePreview }from"../lib/sanity.preview";import Movie from"./Movie";exportdefaultfunctionPreviewMovie({ query, queryParams }:{ query:string, queryParams:{[key:string]:any}}){const data =usePreview(null, query, queryParams);return(<><Movie movie={data}/><Link
className="bg-blue-500 p-6 text-white font-bold fixed bottom-0 right-0"
href="/api/exit-preview">
Exit Preview
</Link></>);}
With this setup, you should now not only see changes to existing documents but even be able to preview pages of new, not-yet-published documents as well!
View entire pages before publishing with the Next.js application in preview mode
Previewing Next.js from inside the Studio
Your setup works excellently so far, but putting these two browser windows side-by-side is more work than it needs to be. Using View Panes, you can embed the Next.js website into an iframe that can place it directly next to the document form editor.
For this, you’ll work in your Sanity Studio‘s folder and install the Iframe Pane plugin.
npm i sanity-plugin-iframe-pane
In your Sanity Studio project, create a new file to handle the Default Document Node, which defines the default settings when the editor for a particular schema type is loaded.
Open up any Movie type document now, and you should be able to show the Next.js website side-by-side with the editor and see draft content in real-time.
Iframe pane can display the Next.js Application directly inside the Studio
Next steps
Write more resilient components by checking existing values before trying to display them. Assume no data exists because the author is previewing a new document. Swap for placeholders or remove the components altogether. For example:
// In preview mode, this title might not yet exist!<h1>{movie.title}</h1>// Swap with a placeholder{movie?.title ?<h1>{movie.title}</h1>:<h1>Untitled</h1>}// Or just remove the component{movie?.title ?<h1>{movie.title}</h1>:null}
Update the options for your Iframe Pane to a function that will render a different URL based on the current document, valid for if you update your Next.js preview route to redirect a URL other than the home page.
In this guide, you’ll see how Sanity separates organizations, projects, datasets, and members by working through a hypothetical example of a growing company that can expand its content model as they grow – without needing a complete overhaul.