Table of Contents

As a developer, I recently went through our shared rite of passage — building a blog. For the answer to “But, why?”, let me redirect you to a write up by James Long which answers it perfectly. At the core of it, side projects can be a fun way to learn new tools and/or how to combine tools together to meet a goal. This is an example of such a project.

This wasn’t the first time I had attempted to build a blog. A year ago, I had spent time trying to achieve this goal using the wonderful Gatsby project. And, well, that did not go so well. I had a brief period of joy due to the simplicity of Gatsby. The beauty of it. It was wonderful! But it did not last long...I spent days configuring the data fetching layer and then some more trying to generate pages dynamically, etc. By day three I had lost all motivation to go any further. So much so, that I shelved the project and moved on with my life, resigned to the fact that I will never have a self-built blog.

Then late last year, I started to feel that need again. The need to bleed? Idk. I was still recovering from the bad developer experience (DX) scars of working on building a blog earlier but for some odd reason, I had the energy to spend on round 2.

I have been a massive proponent of ZEIT for a few years now and I’d say my love for them is quite public. I had been itching to get my hands on Next.js for a while and I finally had the perfect excuse! So I set on, on my journey to build my blog, a second time. This time around though, I was determined to have a developer experience that I absolutely loved and one that prevented me from shooting myself in the foot (which I have a proclivity for).

This post documents my journey.

I knew nothing about building with Next.js. What I did know was a lot of people had been raving about their learn page.

So I dove in.

Module after module, exercise by exercise, I started climbing the Next.js ladder. The simplicty of it reminded me of the good ol’ times with Gatsby. But it scared me for the same reason. I did not want to be baited in by that again! I progressed cautiously.

As I worked through the lessons, I was pleasantly surprised by the simplicity sustaining itself. For example, learning about setting up dynamic routing led to dynamic page generation for free! Soon, I was at the gates of “Fetching Data for Pages” section. I felt a chill go down my spine. Not out of fear this time, but out of excitement!

Dealing with data was an absolute ball with Next.js! Gone was the need for configs, plugins, data sources, etc. I defined a function (getInitialProps) in pages that needed to fetch data and I was good to go! Best part, I controlled how the data was fetched, its shape and everything else about it! It was just a function after all; I could do anything and everything in there! No more having to learn about edges, nodes, etc!

Here’s what that looked like.

// pages/index.js

const Index = ({ posts }) => {...}

Index.getInitialProps = async function() {
  return {
    posts: [
      {
        id: '1234',
        slug: 'first-post',
        title: 'The first post'
      },
      {
        id: '2345',
        slug: 'second-post',
        title: 'The second post'
      },
      {
        id: '3456',
        slug: 'third-post',
        title: 'The third post'
      }
    ]
  }
}

export default Index

YESSS!

I did miss the GraphQL based data layer from Gatsby though. Not the unified schema it has, but the beauty of having the data described as a graph right next to the code that needed it.

Coupling data with presentation is an idea that Gatsby absolutely nailed!

This led to the first realisation —

Ease of dealing with data was paramount!

I mean, it is a blog afterall! GraphQL was my chosen solution to realise this.

“But hold on a second Mudit, where is your data coming from?”, you ask.

Well, let me tell you.

I decided to use Contentful. Why? Because I know it quite well, having worked on it for close to four years. It’s a wonderful product built by an awesome team! Not only does it have an amazing developer experience, the authoring experience is freakin’ baller! But don’t take my word for it. Try it out for yourself! I was up and running, fetching data in no time with contentful.js. At this juncture, I could have used Contentful’s native GraphQL capabilities or I could have shipped a GraphQL server alongside the blog to meet my goal.

The former, while easy and plug & play, would have locked me into a schema I could not control. Also, Contentful’s current pricing does not give users access to the GraphQL API on the free plan. Go figure. 🤷🏻‍♂️ The latter would require a backend which I found unnecessary as all it would do is proxy requests to Contentful. I would end up with higher latency as a request would have to go from the browser to the blog backend to Contentful and back the same way. I decided against both.

I wanted the blog to talk directly to Contentful using my GraphQL schema. Surely, there was a simpler way. I started looking for a library that would let me “do” GraphQL without a server while retaining the authoring experience I had gotten used to from the apollo-* suite of tooling. To my suprise, this was a fairly difficult find. GraphQL Nexus came the closest but I was in the market for a schema first approach. It promotes a code first approach. I came up empty and decided to build something for myself.

And so serverless-graphql was born! With it, I could now run graphql anywhere without a server — queries, mutations and subscriptions included! Now, this is in no way groundbreaking. graphql-js has always been capable of being run anywhere. But I wanted my sweet schema authoring experience and I wanted it bad! With serverless-graphql, I could define my schema and resolvers as I had come to love from having worked with apollo-* tools.

Here’s an example of what that looked like.

// graphql/schema.js

import { getQueryRunner, gql } from '@zeusdeux/serverless-graphql'

export const typeDefs = gql`
  type Query {
    posts: [PostMetadata!]!    
  }
  type PostMetadata {
    id: String!
    slug: String!
    title: String!
  }
`

export const resolvers = {
  Query: {
    async posts(_,__,{ ctfl }) {
      const posts = await ctfl.getEntries({
        content_type: 'blogPost',
        order: '-sys.createdAt'
      })

      return posts.items.map(post => ({
        id: post.sys.id,
        slug: post.fields.slug,
        title: post.fields.title
      }))
    }
  }
}

export const { graphql: runQuery } = getQueryRunner({
  typeDefs,
  resolvers
})

// usage example:
// const ctfl = contentful.createClient({...})
// await runQuery(`{ posts { title } }`, context: { ctfl })

As a bonus, the schema generated by serverless-graphql is compatible with apollo-server. This meant I could bring up a local apollo-server for even quicker iteration on the GraphQL schema. And so I did! With this infrastructure in place, I was able to co-locate my data queries with the code that used it! I had recovered what I was missing from Gatsby. 😍

// pages/index.js

import { gql } from '@zeusdeux/serverless-graphql'
import fetchViaGql from '../graphql/dataFetcher'

const Index = ({ posts }) => {...}

Index.getInitialProps = async function() {
  return fetchViaGql({
    req: gql`
      query getPosts {
        posts {
          id
          slug
          title
        }
      }
    `
  })
}

export default Index

Toit.

jake peralta and charles boyle from brooklyn 99 high fiving

While this was wonderful and all, I was still not entirely happy with the setup.

Thus far, I had been working with the same codebase that I had used to go through the different “Learn” chapters on Next.js’ learn page. It was a combination of JavaScript, React and GraphQL. Nothing more, nothing less.

As I iterated on the code, I found myself spread across multiple files, tools and modes to deal with iterating on the GraphQL schema and other application code. I found myself losing time figuring out the source of errors which varied from typos to just straight up incorrect code. All this context switching was slowing me down.

This lead to the second realisation —

Quick iteration on code while reducing context switching and increasing feedback was non-negotiable.

Now, while I am a self-professed TypeScript fan, I do agree that it’s not just JavaScript with types. It’s a whole new language and has a fairly steep learning curve. So, it isn’t something I recommend to folks that are new to JavaScript or developement in general.

For this project though, I noticed that I had types flying around in my current setup already given that GraphQL was in play. Granted that they were not types at the language level, but the whole data layer was typed using the GraphQL schema definition language! This reminded me of a wonderful post by the Intercom team on how they built their new homepage using Contentful and TypeScript with end to end type safety. They were raving about the brilliant experience it offered to both developers and authors alike!

I took the bait.

I decided to switch the project over to TypeScript to see if I could squeeze out some of that sweet sweet developer and authoring experience. It was as simple as running the following magic incantation in the root of the Next.js project.

npm install --save-dev typescript @types/react @types/node

I renamed the files in the project to .ts and .tsx where necessary. Next.js picked up the change and even generated a tsconfig.json. What even! 🥰

I added a linter and with that + TypeScript, the jump in DX was anything but subtle! My typos and sillyness were being thwarted by tsc and tslint and hot dang, these tools aren’t kind! Nevertheless, better them yelling at me than my 1.5 readers. 🙃

I was now at a stage where, while in theory I could iterate fairly quickly, I was slower than before. Why you ask? Well, because types.

If you recall, the data layer was GraphQL based, which meant I was writing resolvers and hand writing the TypeScript types to match the GraphQL types and vice-versa. This turned out be fairly error prone but more importantly, cumbersome! Where was that sweet DX I was promised? 😢

Turns out, I had blinders on and did not look at what the GraphQL + TypeScript ecosystem had to offer.

I was in for a surpise.

I came across the brilliant graphql-code-generator project by Dotan Simha. It lets you generate TypeScript types given not only from a GraphQL schema but also all your operations and fragments! Freakin’ A!

I pulled that in immediately. All it took was the following command to install the CLI and the TypeScript specific plugins.

npm install --save-dev @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations

Following that, I added a codegen.yml with the following contents and it was good to go!

overwrite: true
config:
  skipTypename: false
  declarationKind: 'interface'
  preResolveTypes: true
generates:
  ./graphql/types.generated.ts:
    schema: graphql/schema.ts
    documents: pages/*.tsx
    plugins:
      - typescript
      - typescript-operations

This configured graphql-codegen to look for a GraphQL schema in graphql/schema.ts and operations + fragments in pages/*.tsx. The generated file contained the following types.

// graphql/types.generated.ts

/** All built-in and custom scalars, mapped to their actual values */
export interface Scalars {
  ID: string
  String: string
  Boolean: boolean
  Int: number
  Float: number
}

export interface PostMetadata {
  __typename?: 'PostMetadata'
  id: Scalars['String']
  slug: Scalars['String']
  title: Scalars['String']
}

export interface Query {
  __typename?: 'Query'
  posts: Array<PostMetadata>
  post: Post
}

export type GetPostsQuery = {
  __typename?: 'Query'
  posts: Array<{ __typename?: 'PostMetadata'; id: string; slug: string; title: string }>
}

export type GetPostsQueryVariables = {}

With graphql-codegen running in watch mode in one terminal pane and tsc running in watch mode in another, I was off to the races! On every change to the GraphQL schema and the operations, the types were re-rendered. This in turn triggered a typecheck and if TypeScript found any issues, it yelled at me both in my terminal and my editor. Not only that, graphql-codegen itself ran schema validation on my queries (courtesy of graphql-js) to find things that TypeScript could not!

For example, I tried to a query fields that were not defined in the GraphQL schema many times on error and that caused graphql-codegen to fail with a GraphQLDocumentError. Alongside that, since the resolvers were now typed using the types generated by graphql-codegen, whenever I forgot a field or added something extra or typo-ed, TypeScript would fail with a typecheck error.

I could now author application code without having to context switch and the feedback from tsc, tslint and graphql-codegen CLI was 👌🏼!

Noice.

But, as greedy as this might sound, I still felt something was missing. The heart wants what it wants 🤷🏻‍♂️

While I was able to iterate very quickly on the application code courtesy of all the tooling and the types provided by Next.js, contentful.js, etc, I found myself breaking the application quite often while iterating on the data model in Contentful.

source

We can’t have that now, can we? I mean afterall, we promised ourselves a brilliant developer and authoring experience!

This is when the third realisation kicked in —

Quick iteration on the data model was not a ‘nice to have’, but a fundamental requirement.

To do so, I figured I’ll take the simplest approach — if the data model changes in “some incompatible way”, break the build locally. Something something fail fast.

Anyway, to do so, I wanted to generate types (yup more of that 🙃) from the data model in Contentful and use them in my GraphQL resolvers. This would catch regressions such as deleting a field that was in use, changing type of fields in some incompatible way (such as text to boolean), etc. These safeguards were good enough for my use case. So I embarked on a quest to look for such a tool. And lo and behold, the folks at Intercom had built such a tool for their new homepage with Contentful project. It’s called contentful-typescript-codegen and it is awesome! It takes Contentful Content Types and generates TypeScript interfaces from it.

Getting it up and running involved setting up a file named getContentfulEnvironment.js in the root of the project, generating a personal access token for Contentful and adding the following contents to the file.

const contentfulManagement = require('contentful-management')

module.exports = function() {
  const contentfulClient = contentfulManagement.createClient({
    accessToken: process.env.CMA_TOKEN
  })

  return contentfulClient
    .getSpace(process.env.SPACE_ID)
    .then(space => space.getEnvironment('master'))
}

I exported the environment variables that it needed and with that set up, I ran the following command to generate the types from Contentful.

contentful-typescript-codegen -o contentful/types.generated.ts

This generated a file named types.generated.ts in the contentful folder in the root of my project. While this worked very well, I was not happy with the output. The interfaces had an “I” prefix which caused the linter to yell, types for all the content types in my Contentful space were generated but I wanted types only for a subset, there were unused imports in the generated file and finally there were quite a few exports that I did not need for my project. While, yes, I could have moved on and used the output as-is, I did not want to add exceptions to the other tooling because a tool was not flexible enough. Instead, I decided to fork intercom/contentful-typescript-codegen and make it more configurable. Now, I did not want to spend time on making it “smart”, but only expose the right knobs and levers to massage its output into a shape I wanted. That was the goal. Which is also why I did not open a PR against the upstream repo as the changes were very specific to my use case. Nevertheless, if the original authors are interested, I am happy to open a PR. 😊

I published my fork to a scoped package, added it as a dev dependency, set up an npm script to run it and et voilà! The massaged output looked as below.

// contentful/types.generated.ts

import { Entry } from 'contentful'

export interface BlogPostFields {
  /** Title */
  title: string

  /** Slug */
  slug: string

  /** Body */
  body: string

  /** Tags */
  tags: Array<...>

  /** Next */
  next?: BlogPost | undefined

  /** Previous */
  previous?: BlogPost | undefined
}

/** A entry of this type stands for a blog post hosted at blog.mudit.xyz */
export interface BlogPost extends Entry<BlogPostFields> {
  sys: {
    id: string
    type: string
    createdAt: string
    updatedAt: string
    locale: string
    contentType: {
      sys: {
        id: 'blogPost'
        linkType: 'ContentType'
        type: 'Link'
      }
    }
  }
}

I now had types being generated from —

  • the GraphQL schema
  • the GraphQL operations made by pages
  • and from the data model in Contentful

The types generated from the GraphQL schema were being used as return types for the resolvers. The types from the data model in Contentful were being used in the resolver bodies to get auto-completion and typechecking against the raw data from Contentful and finally, the types for the operations were being used in the Next.js pages to annotate the type for props being received by the components.

Example resolver —

// graphql/schema.ts

import { ContentfulClientApi } from 'contentful'
import { BlogPostFields } from '../contentful/types.generated'
import { Post, PostMetadata, Query } from './types.generated'

...

export const resolvers = {
  Query: {
    async posts(
      _parent: any,
      _args: any,
      { ctfl }: { ctfl: ContentfulClientApi }
    ): Promise<PostMetadata[]> {
      const posts = await ctfl.getEntries<BlogPostFields>({
        content_type: 'blogPost',
        order: '-sys.createdAt'
      })

      const result: PostMetadata[] = posts.items.map(post => ({
        id: post.sys.id,
        slug: post.fields.slug,
        title: post.fields.title
      }))

      return result
    }
  }
}

...

Example page —

// pages/index.tsx

import { gql } from '@zeusdeux/serverless-graphql'
import { NextPage } from 'next'
import fetchViaGql from '../graphql/dataFetcher'
import { GetPostsQuery } from '../graphql/types.generated'

const Index: NextPage<GetPostsQuery> = ({ posts }) => {...}

Index.getInitialProps = async function() {
  return fetchViaGql<GetPostsQuery>({
    req: gql`
      {
        posts {
          id
          slug
          title
        }
      }
    `
  })
}

export default Index

With all of these bits of tooling in place, not only was the local developer experience brilliant, but also as an author, on mucking around with the data model, I got immediate feedback locally on how the changes impacted the code! Both my personas were happy, shared a beer and lived happily ever after! 🍻

Until they realised that they had to get the project on the i n t e r w e b z z

Let’s take a break from the story to take inventory of what we have so far. We have a working Next.js project that fetches data from Contentful in the getInitialProps functions on the pages that need data, using GraphQL without a server. We have our data queries co-located with the code that uses it and we have no schema lock in! All of this is supported by tooling that auto-generates types and consumes those types to enforce safety and prevent potential footguns.

Not too shabby. 👾

What I wanted next (😬) was auto-deployment to my provider of choice, ZEIT, when either the code or the content changed.

Now, some would argue that rebuilding when the content changes is unncessary as I am using getInitialProps and Next.js will disable Automatic Static Optimization when it comes across this function. You would not be wrong. But, what I have chosen to not share yet is that I always wanted a fully statically generated blog. Next.js doesn’t yet have a stable way of doing so but there is a proposal open for it and you can already use unstable_ prefixed getStaticProps and getStaticPaths as of Next.js 9.2!

So, with that in mind, I wanted to trigger deployments when either the code or the content changed. This was simpler than ordering takeaway (and faster)!

To achieve this, I relied on the trusty old ZEIT Now for GitHub which I already had installed in my GitHub account. Via the UI, I configured it to auto-deploy the blog repository on every push. By default, every successful deployment of the master branch also got aliased to https://blog.mudit.xyz.

Perfect! Nothing more to do here.

Contenful and ZEIT, both support webhooks. ZEIT lets us create deploy hooks which are associated with a project and trigger a deployment of a branch in your git repository for that project. Two clicks later in my project settings on ZEIT, I had a deploy hook that deployed the master branch when it was POSTed to.

Screenshot of ZEIT deploy hook setup screen

Contentful webhooks are incredibly powerful. They can trigger a request to a given endpoint when any combination of actions on entities stored in Contentful takes place. I created one that POSTed to the ZEIT deploy hook from earlier whenever an article was published or unpublished.

Screenshot of Contentful webhook setup screen

With these in place auto deployments were sorted! Friggin’ yay! 🙌🏻

Up until now, I had been relying on the getInitialProps function + my serverless-graphql library to fetch data in pages that needed data. This was already pretty darn fast. But I found it weird that whenever the user navigated on the client side, the same content was fetched from Contentful. The dynamicity, while wonderful, was unnecessary for this project.

Earlier on in this chapter, I alluded to a proposal that brings native SSG capabilities to Next.js. I was delighted to find out that getStaticProps and getStaticPaths were already available behind an unstable_ prefix! I quickly hacked it in place and within a few minutes, my whole blog was being statically rendered! Below is an excerpt from the code that enabled this.

// /pages/index.tsx

import { gql } from '@zeusdeux/serverless-graphql'
import fetchViaGql from '../graphql/dataFetcher'
import { GetPostsQuery } from '../graphql/types.generated'

...

export async function unstable_getStaticProps() {
  const posts = await fetchViaGql<GetPostsQuery>({
    req: gql`
      {
        posts {
          id
          slug
          title
        }
      }
    `
  })

  return {
    props: posts
  }
}

export default Index

The final piece of the puzzle was being able to preview my posts locally before publishing them. This was easy to do as Contentful covers this exact use case with their Content Preview API. I added a conditional to generate the Contentful client config based the environment the project was running in. The code below is taken from the data fetching module of the project.

import { createClient } from 'contentful'

const ctflConfig =
  process.env.NODE_ENV === 'production'
    ? {
        space: process.env.SPACE_ID!,
        accessToken: process.env.CDA_TOKEN!
      }
    : {
        space: process.env.SPACE_ID!,
        accessToken: process.env.PREVIEW_TOKEN!,
        host: 'preview.contentful.com'
      }

const ctfl = createClient(ctflConfig)

All of those env.*_TOKEN, were also added as secrets on ZEIT using now add secrets ... so that they are available when ZEIT builds and deploys the blog. Here’s the relevant part of the config.

This is what the result currently looks like. Production is on the left and my local instance is on the right.

Prod and local blog contents page side by side

not-bad-obama

I agree Mr. Obama! 🤝

This was my journey, directed by a borderline obsession with developer experience which magically ✨ led to a brilliant authoring experience. At the end of my quest, I have —

  • a fully functioning self built blog
  • that meets my developer and authoring experience requirements
  • while teaching me Next.js, GraphQL and TypeScript everyday that I work on it.

I want to note that tools that were meant to get in my way, such as TypeScript, tslint, etc, did so and in doing so protected me from footguns. The Next.js framework itself stayed out of the way; like it was not even there. And that is exactly what I think is a hallmark of a great framework!

Finally, not only did I get a blog out of this journey, like any good journey, I made many friends along the way, such as Next.js, GraphQL, serverless-graphql, contentful-typescript-codegen, graphql-code-generator, ZEIT deploy hooks, Contentful webhooks and many more.

The resulting code can be found on GitHub.

A massive thanks to @bloomca, @timneutkens, @robbiestrom, @khaled_garbaya and @suevalov for reviewing this behemoth of a post!

Now, to write something to put on here.

🖖🏼