On This Page
If you've worked with a content team before (or if you're like me and you want to get everything close to perfect) you know that no one hits publish just to check what their changes look like.
I mean, if you're on WordPress, it's probably not too much of an issue—and you can quickly hit unpublish. But if you're on Jamstack, all sorts of things happen when you hit publish, a webhook probably gets fired off, a new deployment starts, your site gets rebuilt, a social media post automatically gets posted, everyone freaks out — total chaos.
So, what if you could see your changes in realtime in your SvelteKit app, without having to hit publish in Sanity Studio? Well, you can! And it's not that hard to do, but it takes a little bit of setup.
The cool thing is, this will set a cookie. Allowing us to do cool things on the frontend such as showing a banner that says "This page is a draft".
The final code for this article is available on GitHub. Use it as a reference or as a starting point for your own project. Please note that the code that takes care of the preview functionality is heavily inspired by Sanity's Next.js toolkit. If there's enough interest, I can take a look at making it a separate package for SvelteKit users.
Requirements
This guide assumes you're using Sanity V3, but with a little bit of tweaking it should work with V2 as well. Additionally you will also need:
- Beginner to somewhat intermediate knowledge of Javascript, SvelteKit and Sanity.
- Node.js and NPM, Yarn or PNPM installed.
- Your SvelteKit app needs to use GROQ for fetching data from your Sanity.io project.
Installing Sanity.io and SvelteKit
First, we need to create a new SvelteKit project. If you already have a project, you can skip installing SvelteKit.
I personally like to install SvelteKit using the Vite scaffolder. Because it supports Svelte, SvelteKit and a bunch of other cool stuff. But you can also use the official SvelteKit scaffolder (which is npm create svelte@latest my-app
).
Then follow the prompts, make sure to select Svelte and SvelteKit. Using Typescript is highly recommended.
Follow my guide on how to set up Sanity.io Studio V3 with SvelteKit to set up your Sanity.io project and embed it in your SvelteKit app.
Sanity.io uses React for their Studio, so we need to make sure that we have it installed in our project. You can do that by running the following command:
The obstacle is the way
The first challenge I ran into was that SvelteKit—unlike Next.js—doesn't have a built-in preview mode. I decided to take a look at how Next.js implements preview mode and see if I could do something similar. I'm not going to lie, Next.js internals were a little bit overwhelming and it took me a couple of minutes to realize the concept of preview mode. Turns out it's pretty simple.
The second challenge was that Sanity conventiently provides a toolkit for Next.js. But, I couldn't find anything similar for SvelteKit. But I guess us Svelte folks are use to that by now 😆 (I'm not throwing shade on React or the Sanity team—but let's face it—React' ecosystem is just better).
Creating the preview mode
Let's start by creating the preview mode. Besides the Groq query, this is not Sanity specific, and can be used for other CMS's as well.
In Sveltekit, any file in the routes
directory can be an endpoint. But I like to keep specific functionality such as this in a separate folder. So, let's create a new folder called api
in src/routes
and inside that folder, create a new folder called preview
.
Inside the preview
folder, create a new file called +server.ts
. This will be our preview endpoint. You can learn more about +server
files in the SvelteKit docs.
Now, add the following code to the file. This will set a cookie that will be used to determine if the user is in preview mode and redirect them to the page that they're trying to preview.
Woah, that's a lot of code! I've tried to comment it as much as possible, but let's go over the imports.
First, we import the env
variable from $env/dynamic/private
.
This is a variable that SvelteKit provides for us, and it contains all the environment variables that we've defined in our .env
file. We'll use this to get the VITE_SANITY_PREVIEW_SECRET
variable, which we'll use to verify that the request is coming from Sanity Studio. The secret can be any random string but you can also use the command openssl rand -hex 16
to generate a random string in the terminal.
Note that it's prefixed by VITE_
which exposes this secret to the client, I know this is not ideal. But since we render Sanity Studio V3 directly in a SvelteKit route as an SPA, we don't have a way to hide this secret from the client. If you have any ideas on how to improve this, please let me know on Twitter. If you have Sanity Studio on a seperate domain and codebase, you can of course omit the VITE_
prefix and use the SANITY_
prefix instead and this will keep the secret hidden from the client.
Next, we import the setPreviewCookie
function
This comes from a set of functions that'll make it easier to set, get and delete the preview cookie on the server. It looks like this:
Then, we import the getSanityServerClient
function
This is a function that we'll use to get a Sanity client. We'll use this to fetch the data from Sanity. We'll also pass a boolean to this function to determine if we want to use the preview client or the normal client. Don't forget to add SANITY_API_READ_TOKEN
and SANITY_API_WRITE_TOKEN
to your .env
file.
This function looks like this:
Note that the config
imported in the file above is the client config and not the SanityConfig (that holds your deskTool
etc.). Mine looks like this:
Finally, we import the postBySlugQuery
query
This is a Groq query that we'll use to fetch the post. The most important part is that this query returns a slug that we can use in our redirect. Remember that we shouldn't use the slug
query param as that may lead to open redirect vulnerabilities. Instead, we'll use the slug that we get from the query. The query looks like this:
Phew, that's done. And apologies for the lengthy explanation, but I wanted to make sure that you understand what's going on.
Now let's go over how to get previews to work in Sanity Studio.
Setting up the preview in Sanity Studio V3
To get the preview to work, we can hook into the Structure Builder API. You can add any React component to S.view.component
and it will be rendered in the Studio pane and have access to content in the form in real-time.
Create a new file called PostsPreview.tsz
and add it in the folder where all you Sanity config files are. In my case, that's ./src/lib/sanity/components
.
All this is doing is rendering an iframe with our preview endpoint as the src
attribute. We're also passing the slug
and type
query params to the endpoint.
Note: If you use a different domain for your Sanity Studio, you should change the url
variable in the getUrl
function to the URL of your SvelteKit app.
Now if you have followed my previous post on how to set up Sanity Studio V3 with SvelteKit, you should already have a sanityConfig.ts
file in your studio
folder. If that's the case, add the missing imports and the PostsPreview
to the defaultDocumentNode
.
Bringing it all together
Now that we have our preview endpoint, and the PostsPreview.tsx
component hooked up, all there is left to do is to create a page that renders the preview. Luckily for us, this is the same route our post page uses. We just need to sprinkle in some logic to check if we're in preview mode.
Importing the createPreviewSubscriptionStore
function
Now bear with me, because this is the part where I won't explain a whole lot. Because most of the code is modelled after the hooks in the Sanity's Next.js Toolkit package.
This package has a createPreviewSubscriptionHook
hook that will stream the whole dataset to the browser, which it keeps updated using listeners and Mendoza patches. Yeah, I know, it's a lot to take in.
I took some time to transform this hook into a Svelte store called createPreviewSubscriptionStore
. All you need to do is take the contents of this folder, and add it to your project (ideally in the same folder where all your Sanity config files live). Don't forget to install the @sanity/groq-store
package.
Like I mentioned at the start of this guide, if there's enough interest, I'll publish this as a package on NPM.
Done? Brilliant 👏
Adding a SvelteKit server hook
Almost there, all we need is a SvelteKit hook that will populate the event.locals
object. This allows us to easily get context in SvelteKit routes about whether we are in preview mode or not. In the src
folder create a new file called hooks.server.ts
and add the following code.
Also make sure you update your app.d.ts
file to include the previewMode
property so that TypeScript doesn't complain.
Creating the server for the post route
We have finally arrived at the good stuff 🎉
In the src/routes
folder create a new folder called posts
and add a new folder called [slug]
inside of it. This will be the route that will render our post page. Inside the [slug]
folder create a new file called +page.server.ts
and add the following code.
This code will only run on the server. Let me break it down:
- We import the
error
function from SvelteKit, which we will use to throw a 404 error if the post doesn't exist. - We import the
PageServerLoad
type from the+page.server.ts
file. This is a type that SvelteKit uses to automatically type theload
function. - We import the
postQuery
query from ourqueries.ts
file. This is the query that will fetch our post from Sanity. - We import the
getSanityServerClient
function from ourclient.ts
file. And we pass in thepreviewMode
boolean that we set in ourhooks.server.ts
file. This will return a Sanity client that is configured to use the preview client if we are in preview mode.
Then all we need to do is return the previewMode
boolean, the slug
of the post, and the initialData
object. This will be used to hydrate the SvelteKit page.
Creating the client for the post route
In the same folder as the +page.server.ts
file, create a new file called +page.svelte
and add the following code.
The first import, previewSubscription
, is part of the folder I asked you to copy into your project earlier. To create this import, in your $lib/config/sanity
folder, create an index.ts
file (or whatever file name you prefer) and add the following code (again this is the client config that is being passed in, and not the desk config).
This will create a Svelte store that we can use in our SvelteKit page. The previewSubscription
store takes three arguments:
- The
query
that we want to run. In this case it's thepostQuery
that we imported from ourqueries.ts
file. This is the same query that we used in our+page.server.ts
file. - The
params
that we want to pass to the query. In this case it's theslug
of the post. - The
initialData
that we want to pass to the query. In this case it's theinitialData
that we returned from theload
function in the+page.server.ts
file.
The enabled
argument is a boolean that we use to enable or disable the preview subscription. In this case we only want to enable it if we are in preview mode and if we have a slug
.
And that's all! We now have a SvelteKit page that will render a post from Sanity. If we are in preview mode, the page will update in real time as we make changes to the post in Sanity.
Now I realize that this is a lot of code to digest, so if you want to see the full code for this example, you can check out the Github repo. If you have any questions, feel free to reach out to me on Twitter.