To give your content creators the best possible experience, let them see what their content looks like before they press publish. In this guide, you’ll add @sanity/preview-kit to a Remix application and make that possible.
Following this guide, you'll create a new Remix application and a new Sanity project with a preconfigured Sanity Studio. You may be able to follow along with an existing project to add this functionality.
Looking for a complete example project?This complete Remix and Sanity template can be installed from the command line and is fully configured with an embedded Sanity Studio.
TypeScript is optional. All the code examples here are authored in TypeScript, but using it is not necessary for this functionality. You can still use JavaScript but may need to remove the typings from the example code.
You’re somewhat familiar with Sanity Studio and Remix
You’re comfortable JavaScript, React, and the command line.
Create a new Remix application
Using the below, you can initialize a new Remix application from the command line and follow the prompts. This command will install Remix in a new directory, prefer TypeScript and install dependencies.
# from the command line
npx create-remix@latest remix-live-preview --typescript --install --template remix
# enter the Remix application's directorycd remix-live-preview
# run the development servernpm run dev
While this installs Tailwind CSS quickly, it’s not recommended for production. If you end up deploying this app, follow the guide on their documentation to install it properly.
To test that this is working, replace the contents of app/routes/index.tsx with the code below:
// ./app/routes/index.tsexportdefaultfunctionIndex(){return(<main className="flex items-center justify-center min-h-screen">
Soon you'll replace me with Sanity Content
</main>)}
Your localhost:3000 should now look like this:
If your Remix application looks like this, Tailwind CSS from the CDN is working!
Create a new Sanity Studio project
Next, you’ll create a new Sanity Studio for a new Sanity project in its own folder. Using the preconfigured schema from the blog template.
# from the command linenpm create sanity@latest -- --template blog --create-project "Sanity Live Preview" --dataset production
# follow prompts during install# this tutorial uses TypeScript# enter the new project's directorycd sanity-live-preview
# run the development servernpm run dev
For more complete instructions and troubleshooting, our documentation covers how to create a Sanity project.
Open localhost:3333 in your browser, and you should see a new Studio with the Blog template schema already created.
There are currently no documents, though; try creating and publishing a few new post type documents.
Create and publish some content to load into the Remix application
To query and display Sanity content inside the Remix application, you’ll need to install a few packages first.
# in /remix-live-previewnpminstall @sanity/client@^5.2.0 @sanity/preview-kit @sanity/image-url @portabletext/react
This command installs:
@sanity/client: A package to simplify querying from (and sending mutations to) the Content Lake
@sanity/preview-kit: A collection of helpful functions to make live preview simple from inside a React application
@sanity/image-url: A library for creating complete image URLs from just the ID of a Sanity image asset.
@portabletext/react: Renders block content from a Portable Text field in the Studio with props to add configuration options.
With these packages installed, you’ll now need to create a new file to setup your Sanity Client:
// ./app/lib/sanity.tsimport{ createClient }from"@sanity/client";import{ definePreview }from"@sanity/preview-kit";// copy these from your Studio's sanity.config.tsexportconst projectId ="";exportconst dataset ="production";exportconst apiVersion ="2023-01-01";exportconst client =createClient({ projectId, dataset });exportconst usePreview =definePreview({ projectId, dataset });
Protip
Why not use Environment Variables?
In other guides, you may use a .env file to load the Project ID and Dataset name, but this is mainly a convenience to store global values or change them between deployment targets. None of these values above are considered sensitive and are safe to be stored publicly. If later you need a token so that your Sanity Client can read private content or write mutations, you should set up environment variables as recommended in the Remix documentation
Fetching data from Sanity
You’ll now confirm that you can query published documents from Sanity first before setting up live preview to display draft content.
Change the index route to include a loader that will use the Sanity Client to query for all post documents with a slug.
Your home page at localhost:3000 should now look like the image below. If not, your Studio likely has no posts yet! Open up your Sanity Studio, create and publish a few new post type documents and refresh your Remix application.
The Remix Application is now querying our Sanity data
You’ll get a "404 Not Found" page if you click one of these links. To fix this, you’ll need to create another route:
// ./app/routes/$slug.tsximporttype{ LoaderArgs }from"@remix-run/node";import{ useLoaderData }from"@remix-run/react";import Post from"~/components/Post";import{ client }from"~/lib/sanity";exportconstloader=async({params}: LoaderArgs)=>{const query =`*[_type == "post" && slug.current == $slug][0]`;const post =await client.fetch(query, params);return{ post };};exportdefaultfunctionPostRoute(){const{ post }=useLoaderData();return<Post post={post}/>;}
As well as a new Post component to display the data.
Notice how the code in this component checks first if a value exists before displaying any element? This is necessary when working later with live preview, where you cannot guarantee the existence of any value.
Now when you click a link on the home page, you should be taken to a page just like this:
Every published post document with a slug can now display as a unique page
What have we achieved so far?
You now have successfully:
Created a new Sanity Studio with some placeholder content
A Remix application with a home page that lists published blog posts with links to a unique page for each post – displaying rich text and an image.
The next step is to make the Remix application query for drafts and preview content before it is published!
Setup preview-kit
Add a CORS origin for the Remix application
Because our Remix application will make authenticated requests to the Sanity Project, its URL will need to be added as a valid CORS origin.
This can be done inside sanity.io/manage; navigate to the API tab and enter http://localhost:3000
Ensure you check ”Allow credentials.”
Important:
Only setup CORS origins for URLs where you control the code.
You will need to repeat this when you deploy your application to a hosting provider with its URL.
If you see the error “Failed to fetch” from your front end while setting up Live Preview, likely, this step wasn’t performed.
Add a new CORS origin for everywhere Sanity content will be queried with authentication
Create a session cookie in Remix
For the best user experience, you’ll want to ensure that anyone browsing the Remix application in “preview mode” stays in that mode as they navigate between pages. For this, we’ll create a session cookie. For more details, see the Remix documentation.
The session cookie will be written by accessing a specific route in the application. The example below will put the user into “preview mode” by writing a session and then redirecting them to the home page.
While there’s no authentication layer here, only a user that is also logged into the Sanity Studio will see draft content.
Add this extra route:
// ./app/routes/resource/preview.tsximporttype{ActionFunction, LoaderArgs}from'@remix-run/node'import{json, redirect}from'@remix-run/node'import{getSession, commitSession, destroySession}from'~/sessions'// A `POST` request to this route will exit preview modeexportconst action:ActionFunction=async({request})=>{if(request.method !=='POST'){returnjson({message:'Method not allowed'},405)}const session =awaitgetSession(request.headers.get('Cookie'))returnredirect('/',{
headers:{'Set-Cookie':awaitdestroySession(session),},})}// A `GET` request to this route will enter preview modeexportconstloader=async({request}: LoaderArgs)=>{const session =awaitgetSession(request.headers.get('Cookie'))// For a more advanced use case, you could use this // to store a read token from sanity.io/manage
session.set(`preview`,`a-random-string`)returnredirect(`/`,{
headers:{'Set-Cookie':awaitcommitSession(session),},})}
Lastly, you’ll also want to give users an easy way to exit preview mode. For this, create a new component that will make a POST request to that same preview route, clearing the session cookie:
Visitors to /resource/preview will receive a “session cookie” and be redirected to the home page.
Any route's loader function can be configured to check for this cookie and, if found, will return the GROQ query and params to the browser. If the cookie is not found, published content will be fetched server-side.
A user in preview mode will see a brief “Loading…” screen while a fetch is made for draft content – if the user is also currently logged into Sanity Studio – and watch any further changes made in real time.
So to make this work, you’ll need to update your routes to check for this cookie and return a different component if found.
Now if you visit the home page, nothing will have changed. However, if you visit /resource/preview you’ll be redirected to the home page and see the “Exit Preview Mode” button.
You’ll also see draft versions of any of the post titles. Try modifying one in the Studio!
With the Studio and Remix side-by-side, you can see draft content in preview mode
Clicking any of these links will still take you to the published version of the content. The $slug.tsx route needs replacing to enable preview mode:
Currently, your Studio is using the default settings for the Desk Tool plugin. To display the Iframe Pane by customising the defaultDocumentNode in the Desk Tool’s configuration.
Now with any Post document open, you can place the Remix application side-by-side with the document editor and see content changes as you write them.
With the Iframe Pane plugin installed, the Remix application is displayed inside the Studio
In summary, an excellent user experience for your content creators as they can now publish with complete confidence!
Where to next?
Some ideas to improve and extend the experience.
Make the preview route redirect to the current document by appending a ?slug= param.
Secure the preview route, making it check if the slug currently exists in the dataset.
Write a view-only token to the cookie so guests not logged into the Sanity Studio can preview content on the website.
Sanity.io – build remarkable experiences at scale
Sanity.io is a platform to build websites and applications. It comes with great APIs that let you treat content like data. Give your team exactly what they need to edit and publish their content with the customizable Sanity Studio. Get real-time collaboration out of the box. Sanity.io comes with a hosted datastore for JSON documents, query languages like GROQ and GraphQL, CDNs, on-demand asset transformations, presentation agnostic rich text, plugins, and much more.
Don't compromise on developer experience. Join thousands of developers and trusted companies and power your content with Sanity.io. Free to get started, pay-as-you-go on all plans.
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.