Abraham Anak Agung

How to Create Coding Blog with Sapper and Sanity from scratch

By: Abraham Anak Agung

a beautiful girl jumping with chocolate umbrella against yellow wood plank background
Photo by Edu Lauton from Unsplash.com

I start doing blogging this 2020 to sharpened my skill in English and also to better understand with the topics I learned. So please be nice with my English, since this is my first time post :)

Why I choose Sapper and Sanity

At first I didn't know anything about blogging and how to do one. All I ever done is create some short 'diary' in Medium about 4-5 years ago. Currently I'm redoing my portfolio site and adding blogging page. So there is plenty of choice available out there, from just getting data for your blog with simple JSON into CMS and databases.

After some research and podcast listening (while I'm ironing - hey that's rhyme :), especially Syntax.fm by Wes and Scott, I try my luck with Sanity. Sanity is the ultimate content platform that helps teams dream big and deliver quickly . That's their motto and how you can not love it?

For the front-end I choose Sapper , I do it because since last year I really like to work with Svelte . It is so easy to understand and fast and yes , I love how animation become first class in Svelte. You don't need to install any library to create simple page site with cool animation and it comes with spring and transition built-in inside Svelte. If you don't know Sapper, it is Svelte meta-framework just like NextJS or Nuxt for React and Vue respectively.

Without further ado... let's do THIS.

Let's Code

All we need to know right now is some basic JavaScript. But you don't need to built this from scratch since Sanity already provide some cool starter projects to get start with, including the one with Sapper and Netlify. But since I'm always curious how it works, I create this simple blog to understand it better.

At first we need to install sapper in our project. It is quite easy you just need to run this on your favorite CLI (this is from their main page and using degit). I'm going to name the new project sapper-sanity-blog . You can choose any name to start with.

npx degit "sveltejs/sapper-template#rollup" sapper-sanity-blog 
cd sapper-sanity-blog
npm install #or yarn
npm run dev #or yarn dev
# and then open http://localhost:3000

Next we need to install Sanity, we can install it with CLI too. First we need to install it as global package with npm .

npm install -g @sanity/cli

And then inside our sapper-sanity-blog directory we can start our sanity project with

sanity init

If you don't have an account, you can register first before continuing. This command will give you couple of question. Since we will build new project than choose:

>Create new project
Your project name: blog #you can choose any name you like
Use the default dataset configuration? Y
# creating dataset...
Project output path: #you can choose any path or just press enter
Select project template: #choose Blog(schema)

#after it finished creating files, start the studio
sanity start
# then open https://localhost:3333

Understanding Sanity Schema

When you create the project template with Blog (schema), you need to understand what is this schema is. Open up your favorite code editor and open our sapper-sanity-blog project folder. Inside blog folder, we can see our Sanity blog structure like this:

Sanity blog folder structure

It contains a bunch of folder and files but the most important one will be in schema folder. Let us see what is inside.

Files inside Sanity schema folder

So there is five main files in there and each one already represent itself by its name. Like author.js is a schema for author, so does category and post. The other two which is blockContent.js is a schema about your text content, just like the one you are reading now. The schema.js file is a schema creator where all those different schema that you had defined is combined and created here.

To understand Sanity schema let us take a good look at the simple one to understand it. Open category.js and you will see it just export JS object with some required property like: name, title, type and fields .

  1. The name(required) property is an identifier for this document type used in the api.
  2. The title property is the display name for the type.
  3. The type(required) property is the type of this document. You can learn more about Sanity Schema Types .
  4. The fields property is what is this category document consist of. In category document it only consist of two fields that you can give input like Title of the category and description of the category . You can easily add another field inside it like maybe an emoji for the category?
// studio/schemas/category.js
export default {
  name: 'category',
  title: 'Category',
  type: 'document',
  fields: [
      name: 'title',
      title: 'Title',
      type: 'string',
      name: 'description',
      title: 'Description',
      type: 'text',
      name: 'emoji',
      title: 'Emoji',
      type: 'string',

It just as simple as that. If you want to know more about Sanity schema types go to their official docs about schema-types . Their docs is pretty awesome.

Other property is important is preview property which can give you the power of custom preview in the second column in your Sanity studio. If you open author.js or post.js , then you will see preview property that contains some other property like select and you can select which property you want to preview.

You can read more about schema or content modelling here .

Before creating anything with our front-end, why not populate our blog with some content and later we can show it in our front-end site. If you already open localhost:3333 then you will likely see something like this:

screen shot of sanity studio with login component

You can login with whatever method you used when you register to Sanity before. After login, go to the desk tab and this is our screen will looks like:

sanity studio desk tab

First we want to create our author and category before jumping and create our post. Click the author link and it will navigate you to the author preview then click Create new author on the top right icon. Fill all the input forms and click publish at the bottom when you finish. Do the same thing with category field. After that you can write some dummy or maybe real thing in the post field. Don't forget to click Publish after you are done.

Preparing Sapper

Now it is time for us to prepare our front-end. Before we can get our content from Sanity, there is couple of packages we need to install to connect our front-end with Sanity.

npm install --save @sanity/client @sanity/image-url groq
npm install --save-dev @movingbrands/svelte-portable-text @sanity/block-content-to-hyperscript @rollup/plugin-json

First thing first, in order for rollup to import json file, we need to modify our rollup.config.js . Add json() plugins in the client and server plugins props:

// rollup.config.js
// ... another import
import json from '@rollup/plugin-json'

  ...other code

export default {
  client: {
    input: config.client.input(),
    output: config.client.output(),
    plugins: [
  server: {
    input: config.client.input(),
    output: config.client.output(),
    plugins: [

Now it is time to create the Sanity client. You can create it inside src/ (since we going to need this client on couple of our svelte component) and named it sanityClient.js . In this file we will create our own client that will take sanity project ID, dataset and useCdn. More about sanity client here .

// src/sanityClient.js
import sanityClient from "@sanity/client";
import { api } from "../blog/sanity.json";

export default sanityClient({
    projectId: api.projectId,
    dataset: api.dataset,
    useCdn: true,

After that, you can go to src/routes/blog/ directory and delete all the js file, NOT the svelte file because we are going to need those. Now if we open blog/index.svelte file, you can see there is two script inside it. One with context='module' and the other without. What is the different? If you know Svelte, you can skip the next paragraph.

The context='module' is part of component format describe here . That script tag runs once when the module first evaluates and the values inside this block are accessible from a regular script tag, but not vice versa. In Sapper, the preload function is a special function where you can access the page path, params, and query .

Since we do want to fetch our blog content inside our blog page, let's begin with routes/blog/index.svelte file. Inside script tag with context='module' , we need to fetch sanityClient inside our preload function.

// routes/blog/index.svelte
<script context='module'>
  import client from '../../sanityClient'
  export function preload({ params, query }) {
    return client.fetch('*[_type == "post" && defined(slug.current)]|order(_updateddAt desc)')
    .then(posts => {
  		return { posts };
  	}).catch(err => this.error(500, err));

Wait a minute, what are we fetching here? It looks like some Mars language or Klingon perhaps? Nope, it is just GROQ (Graph Relational Object Queries) . It is specific to Sanity and you can learn more about this awesome query language at Sanity website or open localhost:3333/vision . It will take you to a playground similar like graphiql .

For now let me slice the string and explain what is going on with that query.

  1. * : asterisk always mean everything (does it?).
  2. _type == 'post' : this mean every documents with type post.
  3. defined(slug.current) : this is a function that will return a boolean value based on the arguments.
  4. order(_updatedAt desc) : sorting the posts result by updatedAt value in descending order.
  5. && : logical and operator - you know what it did.
  6. | : pipe operator passes the left hand array result to the right hand component and returns the result.

It is pretty easy to understand, but it may takes some time to write it.

Now in the regular script tag, you can access the return value from the preload function in the posts variable. The posts variable will contains an array of your blogs posts, so there is not much to change in our html, except the href from {post.slug} to {post.slug.current}

// src/routes/blog/index.svelte
<li><a rel="prefetch" href="blog/{post.slug.current}">{post.title}</a></li>

If you want to know the data that we got, this is what posts were structured:

// posts
    "author": {},
    "body": [{}, {}],
    "categories": [{}],
    "mainImage": {},
    "slug": {},
    "title": "",
    "_createdAt": "",
    "_id": "",
    "_rev": "",
    "_type": "post",
    "_updatedAt": ""
  // ...

For the src/routes/blog/[slug].svelte file we could also do the same with preload function.

// src/routes/blog/[slug].svelte
<script context="module">
	import client from '../../sanityClient'
	import BlockContent from "@movingbrands/svelte-portable-text";

	export async function preload({ params }) {
		// the `slug` parameter is available because
		// this file is called [slug].svelte
		let post;
		try {
			const filter = '*[_type == "post" && slug.current == $slug][0]'
			post = await client.fetch(filter, {slug: params.slug})
		} catch (error) {
			this.error(500, err)
		return {post};

In order to render the post, we need BlockContent to parse the data we got. Later you can add custom serializers in you BlockContent, for now change the HTML into this:

<!-- src/routes/blog/[slug].svelte -->
<div class="content">
	<BlockContent blocks={post.body} />

Now if you open localhost:3000/blog you can see the title of the post that you already publish before. Click it and you should see the content. You can add new post, update the old one or delete it. Feel free to play with your new blog!


I'm still exploring Sanity since I'm new to their products and services, but as my experience building this blog, I could say that Sanity is awesome! Especially GROQ, there is so much thing we can do with it and I still don't know about Image hotspot in Sanity. So expect more blog post about that.

Thank you for the people at Sanity for building this awesome services. Much of the code is from the Sanity Sapper official starter .

Untill then... see ya...