Skip to the content

Journal

Blog with Sanity.io and 11ty

This website has been recently rebuilt with 11ty. I quite like having a simple setup without a CMS for my personal site. It’s flexible and easy to experiment with I did however want a simple way to write and manage articles. The ideal time to run a little experiment with Sanity.io.

It’s a content platform similar to a content management system (Wordpress, CraftCMS, …) but without a front-end attached. You simply define your content structure which you can then access from whatever system you want. It’s really lightweight and quick to set up.

I created a free account that will cover creating a personal blog. I’m not going to describe the process of setting up the studio as they do an excellent job with their getting started documentation.

As a starting point I created a simple schema where I opted to use the markdown editor instead of the default richt text editor:

export default {
  name: 'post',
  title: 'Post',
  type: 'document',
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: 'string',
    },
    {
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96,
      },
    },
    {
      name: 'publishedAt',
      title: 'Published at',
      type: 'datetime',
    },
    {
      name: 'body',
      title: 'Body',
      type: 'markdown',
    },
  ]
}

Once I got it set up I deployed it with Netlify. It’s nice that they provide the option to self-host the client. The API itself is hosted by Sanity.

One important thing to note is that you need to define the access settings to the API under the “CORS Origin” settings in the Sanity dashboard. If you self host you need to give that domain write access.

There's some setup involved for getting the content from the Sanity API.

  1. Setup the sanity client in 11ty
  2. Collecting the data
  3. Generating the pages in 11ty
  4. Creating an article template
  5. Adding an overview page

First, you need the sanity client as a dependency

"@sanity/client": "^2.0.0"

Add an .env.development file to store the sanity token for local development. For production you need this .env file on your server or in a Github secret.

SANITY_READ_TOKEN=[YOUR_TOKEN]
SANITY_PROJECT_ID=[PROJECT_ID]
SANITY_DATASET=[DATA_SET]

Provide a client-config.js file in the root of your project.

module.exports = {
  sanity: {
    projectId: process.env.SANITY_PROJECT_ID,
    dataset: process.env.SANITY_DATASET,
    apiVersion: '2021-08-31',
    useCdn: true
  }
}

Finally, setup the sanity client in utils/sanityClient.js.

require('dotenv').config({
  path: `.env.${process.env.NODE_ENV || 'development'}`
})

const sanityClient = require("@sanity/client");
const { sanity } = require('../client-config')

module.exports = sanityClient({
  ...sanity, 
  useCdn: !process.env.SANITY_READ_TOKEN, 
  token: process.env.SANITY_READ_TOKEN
});

Adding a post.js file in the data folder of your 11ty project allows you to query the Sanity API. Here we get the published date, title, slug and body of a post.

const groq = require('groq')
const client = require('../../utils/sanityClient.js')
const hasToken = !!client.config().token

function generatePost (post) {
  return {
    ...post,
  }
}

async function getPosts () {
  // Learn more: https://www.sanity.io/docs/data-store/how-queries-work
  const filter = groq`*[_type == "post" && defined(slug) && publishedAt < now()]`
  const projection = groq`{
    publishedAt,
    title,
    slug,
    body
  }`
  const order = `| order(publishedAt asc)`
  const query = [filter, projection, order].join(' ')
  const docs = await client.fetch(query).catch(err => console.error(err))
  const preparePosts = docs.map(generatePost)
  return preparePosts
}

module.exports = getPosts

To generate the pages, create a journal.njk (the name of your article collection) file. This will output a collection of articles.

---
layout: post
section: Journal
permalink: journal/{{ post.slug.current | slugify }}/index.html
pagination: // The pagination generates the pages from the data
  alias: post
  data: posts
  size: 1
  addAllPagesToCollections: true
eleventyComputed: // You need to process this data to use it outside of the page content (ex. in meta data or a RSS feed)
  title: "{{ post.title }}"
  content: "{{ post.body }}"
---

Create a layout for your articles. Here I also added a previous/next article navigation at the end of the current article.

---
layout: base
---

<article>
  <p><a href="/journal/">Journal</a></p>
  <h1>{{ post.title }}</h1>
  {{ post.body | markdownify | safe }}
  <hr />
  <p>Published on <time datetime="{{ post.publishedAt }}">{{ post.publishedAt|readableDate }}</time></p>
</article>

<div>
  {% set previousPost = collections.journal | getPreviousCollectionItem(page) %}
  {% set nextPost = collections.journal | getNextCollectionItem(page) %}

  {% if nextPost %}
  <h2>Continue</h2>
  <p>
    <a href="{{ nextPost.url }}">{{ nextPost.data.title }}</a>
  </p>
  {% elseif previousPost %}
  <h2>Continue</h2>
  <p>
    <a href="{{ previousPost.url }}">{{ previousPost.data.title }}</a>
  </p>
  {% endif %}
</div>

Finally, an overview page gives you an overview of the articles. These are generated from the collection we created.

---
layout: base
title: Journal
eleventyNavigation:
  key: Journal
pagination:
  data: collections.journal 
  size: 10
  reverse: true
permalink: "journal/{% if pagination.pageNumber > 0 %}page-{{ pagination.pageNumber + 1 }}{% endif %}/index.html"
---

<div>
  <div>
    <div>
      <h1>Journal</h1>
      <p>Thoughts, experiments and other ramblings.</p>
      <ol>
      {% for post in pagination.items %}
        {% set currentPost = post.data.post %}
        {% if post.url != url %}
        <li>
          <h2>
            <a href="{{ post.url | url }}">{% if currentPost.title %}{{ currentPost.title }}{% else %}<code>{{ post.url }}</code>{% endif %}</a>
          </h2>
          <div>
            {{ currentPost.body | truncate(200) }}
          </div>
          <p>
            <time datetime="{{ currentPost.publishedAt }}">{{ currentPost.publishedAt|readableDate }}</time>
          </p>
        </li>
        {% endif %}
        {% endfor %}
      </ol>

      {% if pagination.pages.length > 1 %}
      <nav aria-labelledby="journal-pagination">
        <h2 id="journal-pagination" class="u-sr-only">Journal pagination</h2>
        <ol>
          <li>
            {% if pagination.href.previous %}
              <a href="{{ pagination.href.previous }}">
                Previous
              </a>
            {% else %}
              <span>
                Previous
              </span>
            {% endif %}
          </li>
          {%- for pageEntry in pagination.pages %}
          <li>
            <a href="{{ pagination.hrefs[ loop.index0 ] }}"{% if page.url == pagination.hrefs[ loop.index0 ] %} aria-current="page"{% endif %}>
              Page {{ loop.index }}
            </a>
          </li>
          {%- endfor %}
          <li>
            {% if pagination.href.next %}
              <a href="{{ pagination.href.next }}">
                Next
              </a>
            {% else %}
              <span>
                Next
              </span>
            {% endif %}
          </li>
        </ol>
      </nav>
      {% endif %}
    </div>
  </div>
</div>

Finally I wanted to trigger an 11ty build if I publish a post in Sanity. I already had a Github action set up to build and deploy 11ty on push. Sanity provides webhooks so the process is pretty painless.

on:
  push:
    branches:
    - main
  repository_dispatch:
    types: [deploy] // This is the trigger we will use in the webhook
jobs:
	 ...:

You can create a webhook in the “API” section of the Sanity dashboard.

To send this to Github I used the github API.

  • URL: https://api.github.com/repos/[USER_NAME]/[REPO_NAME]/dispatches
  • Trigger on: create, update and delete
  • Filter: _type == "post" && defined(slug) && publishedAt < now()
  • Projection (the POST body): {"event_type": "deploy"}
  • HTTP method: POST
  • HTTP headers:
    • Accept: application/vnd.github+json
    • Authorization: token [PERSONAL_ACCESS_TOKEN]
  • Make sure you create a github PERSONAL_ACCESS_TOKEN with access to the REPO and WORKFLOW.

I must say I enjoyed using Sanity with 11ty. It was pretty straight forward and the way it allows you to craft the setup to your liking is really nice. There’s still some stuff I have to figure out like using the rich text editor, images and generating category pages but so far I’m happy with the result. I have the benefits of a static site and the ease of a CMS for writing articles.


Written on 

How to get started?

Uncertain about your next steps? Let's start a conversation: