Getting JSON data from Sanity.io with a lightweight script

Or how to feed Hugo, Eleventy, Jekyll, Bridgetown and other SSGs from Sanity

JSON, being such a flexible and widespread format, is commonly used by Static Site Generators (SSGs) to feed data into their HTML templates. This is the case of Hugo, Eleventy, Jekyll, Bridgetown and most others from the 330+ generators listed in jamstack.org.

Thankfully, Sanity.io is JSON-first. Your data is stored in it, GROQ (the querying language for fetching from your dataset) understands JSON natively, and even your content models are defined through Javascript objects!

Note

In this article, I won't cover centralized GraphQL systems like the one used by Gatsby, or frameworks that fetch data on each route or user request like Remix or NextJS.

Here's the high level view of how to create JSON files from Sanity data during builds to feed SSGs:

  1. Run a script before the SSG builds
  2. This script will connect to Sanity through one of its client libraries
  3. Fetch data from the GROQ queries you define
  4. Save those to the proper files in disk
  5. Then run the generator's process and access the data from your templates

Keep in mind that 1. could happen in a process native to your generator, such as ElderJS's bootstrap hook and Eleventy's many data fetching approaches.

Below is my current go-to script for doing 1-4 in NodeJS with the @sanity/client package:

// NodeJS-based GROQ queries that are persisted into the filesystem
import sanityClient from "@sanity/client";
import fs from "fs";
import path from "path";

// @TODO: Update with your project's config
const client = sanityClient({
  projectId: "tvkfaenh",
  dataset: "production",
  apiVersion: "v2022-02-26",
  // As this runs in a static generation context, we can afford not using the CDN to always get the freshest data
  useCdn: false,
});

// @TODO: write your GROQ queries here
const QUERIES = [
  {
    filename: "genus.json",
    query: `*[_type == "genus"]`,
  },
  {
    filename: "homepage.json",
    query: `{
      "hero": *[_type == "homepage"][0],
      "genus": *[_type == "genus"][0..3],
      "settings": *[_type == "settings"][0]
    }`,
  },
];

const promises = QUERIES.map(({ filename, query }) => {
  return new Promise(async (resolve, reject) => {
    try {
      // 1. Get the data from Sanity
      const data = await client.fetch(query);

      // 2. Save that as JSON to disk
      fs.writeFileSync(
        path.join("data", filename),
        JSON.stringify(data, null, 2)
      );
      resolve(true);
    } catch (error) {
      console.error(`${filename} went wrong`, error);
      reject();
    }
  });
});

async function getData() {
  await Promise.allSettled(promises);
}

getData();

If you aren't comfortable with it, my interactive article on GROQ may help! Let me know if you have questions or improvements to the script above meet@hdoro.dev or hdorodev 😊

Note

I initially shared this with Larry Swanson in our session on connecting Sanity to Hugo. Check it out if you want to go into the thought process behind this.

Using the data in SSGs

Each generator will have its own approach to loading & using data into it. Some examples:

  • Hugo & Eleventy automatically insert data into templates from JSON files in the data directory
  • Jekyll, Bridgetown & Hexo have the _data folder

More commonly, generators like ElderJS, Astro and full-stack frameworks like Next, Nuxt, SvelteKit & Remix have their bespoke ways of loading data on a per-route or per-request basis. The method I describe above isn't recommended for these stacks as you'd be over-fetching data from loading all content for every page.

Do reach out if you have questions about specific integrations, it'd be a pleasure to help! You can book a free mentoring session here 😉