Shopify + Sanity: Read about the investment and partnership –>

Brennan Harris Personal Blog

By Brennan Harris

A mommy blog with citizen journalism and pop lit mixed in.

The blog list, populated with the PicoSanity client and Sanity's CDN option enabled

About the project

This was a holiday break project to migrate and refactor my personal blog from running on Django with a brittle WSGI implementation in an AWS Lightsail instance with lots of manual push/pull and service restarting


a modern dev/test/deployment stack served from Vercel with the application written in Svelte using SvelteKit. And Sanity as the CMS, which holds blog entries and subscribers.

Almost everything about the integration with Sanity was super smooth. I've never developed using React, so I was a little intimidated with configuring the Sanity Studio and creating custom preview elements, but this video and this one were great primers on development inside of Sanity. Taking on both GROQ and PortableText was surprisingly easy. Sanity has good documentation and intro videos and the community admins and other users on the Slack channel were super available for feedback.

Getting the PortableText to render in SvelteKit wasn't quite as obvious. I ended up using the @portabletext/svelte library for my front-end rendering. One thing that threw me off was the example code showed something like blocks={[ // Portable Text array ... ]} as the example prop structure imported to the <PortableText /> component, where in reality, the default structure served from Sanity already contains the inner array brackets, so your actual reference will probably look more like <PortableText blocks={block_variable} /> if you're just passing the result from the Sanity client through.

I wasn't familiar enough with the object distinction between marks, blocks, and assets when I started cobbling the PortableText sequelizers together, so I just initialized each serializer component applying JSON.stringify to the PortableText object that rendered it to see the data structure that got passed in. From there, it was easy to separate and style my components to get the block content rendered just how I liked it.

When I published posts in my old implementation, I would write HTML directly into a text field through the default Django admin page, then just render the HTML ({{ content_var|safe }} in Django). This was very cumbersome, writing tags into the page for every italic and line break. Sanity block content editing is a great improvement by comparison. On the other hand, having the flexibility to inject unique script and style elements inline in a single blog post allowed me to add fun little widgets into my posts without much effort. To preserve that flexibility in Sanity, I was able to add a custom HTML schema object and render that block with a serializer that looks something like {@html portableText.block.body} in Svelte (I know this looks scary, but I'm the only one who has access to my Sanity Studio).

For some reason, running svelte-kit dev resulted in a thorny package incompatibility error that caused fetch requests through the Sanity client to fail. It must've been some Vite thing, because once I built and ran preview, it worked fine. I talked to other users on the Sanity Slack channel who experienced the same issue. With their help, I was able to resolve the issue by using the library PicoSanity for fetches, and the full Sanity client for writes. With this combination, I'm able to develop and build without any problems.

The cherry on top was getting my subscriber list and private blacklist migrated to Sanity. I was able to make an .ndjson file copied out of my Django admin panel and upload it with the Sanity CLI uploading utility without the need to write scripts to transfer structured data over, which saved me an hour or so.

This is my first time really serving an application written in SvelteKit. The block content and useability of Sanity serves my needs well, and the PicoSanity hack isn't a dealbreaker for me. Reach out if you're interested in the Sanity/SvelteKit/Vercel triumverate (@brennanxyz).