Main image of post

Written by Øyvind Monsen

Building a blog with Sanity and React

Introduction

Sanity is a free, customizable cms that is easy to integrate with any application, for example a blog. They also offer a lot of guides for how to do this, which is how I managed to build this blog in the matter of a couple of days. Since there are so many great guides available this will not be a full guide, but I encourage you to check some out at sanity.io/blog. This will rather be a walkthrough of some highlights in my progress of making the blog you are reading now.

Getting started

Getting started with Next.js and Sanity is really simple. You first setup a Next project, and inside of it run the sanity init command. This gives you your own, fully customizable build of Sanity Studio. You can run it locally, or deploy it to a *.sanity.studio domain with

1$ sanity deploy

All the data you publish will be available right away, both from the local and deployed version.

For the blog I needed dynamic routing, since I want to be able to navigate to specific blog posts. Next makes this easy. Inside the pages folder I added a blogpost folder with the file [slug].js this will route all requests to /blogpost/some-blog-slug will be redirected to the blog post with the slug "some-blog-slug".

Getting some data

Sanity uses a query language called Groq. To get the list of posts you simply do as follows

1sanityClient.fetch(
2  `*[_type == "post"]{
3      title,
4      slug,
5      mainImage{
6        asset->{
7          _id,
8          url
9        }
10      }
11    }`  
12);

Getting a single post is similar, but you also get the block content so you can render the actual content. But when do we get this data? When I started out I went with serverside rendering (SSR). That way whenever a user visits the site they get completely updated data from Sanity. In Next SSR is done with adding a getServerSideProps(context) function to the page file. But why not just get the data in the browser with React you ask? The answer is search engine optimization (SEO). WWhen search engines like google index pages they send out bots that get information about pages, and find pages that are linked to. When you do client side rendering what the bot gets is the unpopulated template, and it wont wait for the javascript to get the data and populate the page. That is why I went with Next.js because it provdies a great solution for SSR.

Rendering data

Now that the data is fetched we render it. In Next we do this quite easily with React. It is quite easy to set up the basic information like title, image and so on, but the block content from Sanity is a bit different. Since it can contain images, formatting and much more it comes as a list of blocks. Thankfully, Sanity has provided us with a plugin called block-content-to-react which will render this block content correctly for us.

1const BlogPost = ({ post }) => {
2  const content =
3    post === null ? (
4      <h2>404 - not found</h2>
5    ) : (
6      <div>
7        <Head>
8          <title>{post.title}</title>
9        </Head>
10        <header className="mb-10">
11          <img
12            className="w-full object-cover h-80"
13            src={urlFor(post.mainImage).url()}
14            alt="Main image of post"
15          />
16        </header>
17        <div>
18          <article className="md:px-20 lg:px-52 px-5">
19            <AuthorInformation name={post.name} />
20            <h1 className="font-serif text-5xl lg:text-6xl mb-10 text-gray-700">
21              {post.title}
22            </h1>
23            <BlockContent blocks={post.body} serializers={serializers} />
24          </article>
25        </div>
26      </div>
27    );
28  return <GeneralPage pageLocation="blog">{content}</GeneralPage>;
29};

I also pass a serializer to the BlockContent. This is to render the code blocks correctly with react-syntax-highlither. The codeblocks themself are also not included by default in sanity studio, but can easily be added as demonstrated in this guide.

Styling the block content

You might also notice that the page styling is done with TailwindCSS. This is a great and easy way to style your sites, and is responsive out of the box. One problem I had was that Tailwind resets all styles of headers etc. are removed so that all styling is intentional. However this made the block content loose all styling, since we cannot style each element directly. Luckily you can override this in css as follows.

1@tailwind base;
2h1 {
3  @apply text-5xl;
4  @apply font-bold;
5}
6
7h2 {
8  @apply text-4xl;
9  @apply font-bold;
10}
11
12h3 {
13  @apply text-3xl;
14  @apply font-bold;
15}
16
17h4 {
18  @apply text-2xl;
19  @apply font-bold;
20}
21
22a {
23  @apply text-blue-600;
24}
25
26a:hover {
27  @apply underline;
28}
29
30blockquote {
31  @apply border-l-2 border-gray-200 pl-2 text-gray-500 italic;
32  @apply text-lg;
33  @apply py-2;
34  @apply my-2;
35}
36
37@tailwind components;
38@tailwind utilities;

Performance

As I mentioned earlier I initially went with SSR. This gives us a bit of a performance hit, since the page needs to be rendered on the server at each request. Since each blog post will change quite seldom this means rendering the exact same content over and over. Next also supports Static Site Generation (SSG). This will get all data at build time and generate the html for all the paths that you have. You do this with getStaticProps(context) and getStaticPaths.

Now we have a new problem however;New content and updates will not be fetched without a rebuild of the site. Again Next has a solution for us. By providing a fallback strategy, and setting a revalidate timer we can make Next render new pages that users request on the fly, and revalidate pages whenever the request is after a given time since the last render. This approach is called Static Site Regeneration. Beware that not all hosting providers will support this, but a great and easy solution, which I went with, is Vercel.

The props and paths methods then becomes

1export async function getStaticProps(context) {
2  const { slug = "" } = context.params;
3
4  const post = await sanityClient.fetch(
5    `*[slug.current == "${slug}"]{
6            title,
7            slug,
8            mainImage{
9                asset->{
10                    _id,
11                    url
12                }
13            },
14            body,
15            "name": author->name,
16        }[0]`
17  );
18
19  return {
20    props: {
21      post: post,
22    },
23    revalidate: 10,
24  };
25}
26
27export async function getStaticPaths() {
28  const posts = await sanityClient.fetch(
29    `*[_type == "post"]{
30      slug,
31    }[0...20]`
32  );
33  const paths = posts.map((post) => {
34    return { params: { slug: post.slug.current } };
35  });
36
37  return {
38    fallback: "blocking",
39    paths: paths,
40  };
41}