Using Sanity.io data in SvelteKit projects

How to fetch data from your Sanity content lake and use it to feed Svelte templates

From a high-level, the process is simple:

  1. Create a Sanity client with your project's configuration
  2. From SvelteKit's endpoints, use this client to fetch data with GROQ
  3. Render this data with Svelte from the endpoint props passed to pages
  4. Ensure the data is there before rendering pages

Let's get into the specifics - you can also follow my video going through this process step-by-step.

Creating the Sanity client

Start by installing @sanity/client in your SvelteKit project:

npm i @sanity/client
# or yarn add

Then, create a sanityClient.js|ts file exporting a configured client with your project's info.

// sanityClient.js
import sanityClient from "@sanity/client";

const client = sanityClient({
  projectId: "YOUR_PROJECT_ID",
  dataset: "YOUR_PROJECT_DATASET",
  apiVersion: "2022-03-24", // choose the API version you want
  useCdn: true,
});

export default client;

Refer to @sanity/client's documentation for the options you configure. Put this file where you can access from routes - most SvelteKit apps have the src/lib folder configured with a $lib alias, which is a great option.

You can also configure the client with SvelteKit's environment variables if you don't want to have project tokens in the code or need to have different test environments.

Fetching Sanity data from endpoints

Assuming you have a homepage at src/routes/index.svelte that you want to feed with Sanity content, create a src/routes/index.js|ts file to fetch data for this page - this will be your page endpoint.

For data fetching, we want to use the get function and use our Sanity client to run a GROQ query that picks exactly the desired data:

import client from "../sanityClient";

export const get = async () => {
  const genuses = await client.fetch(/* groq */ `*[
    _type == "genus"
  ][0..10]{
    _id,
    name,
  }`);

  return {
    body: {
      genuses,
    },
  };
};

Alternative

The previous approach to fetching data in SvelteKit routes was to use a load function in a <script context="module"> and access an API endpoint from there (a "standalone endpoint").

Although that works, page endpoints (introduced in next.260 as shadow endpoints) are simpler, lead to less duplication, and make it easier to handle 404s and 500s - so use that if you can 😊

To make sure the above is working, you can add a console.log before returning the endpoint's body. If the data is undefined or null, make sure your client configuration is right and test your GROQ query in the Sanity studio's Vision plugin.

You can also have route-dependent data by accessing a route parameters. Here's an example for the routes/genuses/[_id].js endpoint:

import client from "../../sanityClient";

export const get = async (event) => {
  // Access the _id from the params object ✨
  const { _id } = event.params;
  const genus = await client.fetch(
    /* groq */ `*[
      _type == "genus" &&
      _id == $_id
    ][0]`,
    { _id }
  );

  if (!genus?._id) {
    return {
      status: 404,
    };
  }

  return {
    body: {
      genus,
    },
  };
};

Plug

Notice how we use $_id in the query above? Refer to my GROQ course's section on query parameters if you're unclear on its meaning 😉

Rendering data in pages

In the SvelteKit page route that matches the page endpoint above, add export let PROP_NAME for all of the props you return in your get function's body. Now you can access your data in your front-end 🎉

<script>
  export let genuses = [];
</script>

<ul>
  {#each genuses as genus}
    <li>
      <a href={`/genuses/${genus._id}/`}>{genus.name}</a>
    </li>
  {/each}
</ul>

Warning: check data's validity before rendering pages

In the genuses/[_id].js endpoint example above you'll notice I return a { status: 404 } if there's no genus returned from Sanity. This makes sure a request to my-site.com/genuses/a-fake-genus-that-doesnt-exist would return a 404 instead of a blank or broken page.

When working with dynamic content, it's a good practice to always check if the keys you expect are there and their values are valid. It's often the case that a reckless component doesn't do these checks, accessing something like optionalObject.property and ends up leading its parent page to a 500 as Javascript throws an error.

And that's it! Reach out if you have any questions meet@hdoro.dev or hdorodev