2022-10-06

How I Built My Blog

I had two priorities when building this blog:

1. I wanted to learn the nitty gritty of a functioning web application.

  • How does data get sent from one place to another?
  • Where do I add all of the social shares and open graph settings?
  • Etc.

2. I wanted it to be fully customizable and relatively easy to add new functionality as I come across cool things online.

Essentially, I used this project to apply the theories and skills I've been learning to create something greater than the sum of its parts and as a testing ground for anything I learn. So far, it has exceeded both of these expectations and I'm quite proud of it.

The Stack

This blog is a Next.js application.

All blog posts and book notes are fetched from Contentful CMS. Next.js has a nifty getStaticProps function that allows you to fetch data before the page is built, making it blazing fast.

For styling, I rely on Tailwind CSS (with some custom CSS for things like the scrollbar). If Tailwind didn't exists and Bootstrap was the only option, I probably would've used vanilla CSS because as a designer, Bootstrap is not as aesthetic as Tailwind.

The most important part of my blog is how I render the body content of the blog posts. I use markdown-to-jsx which allows me to write posts in  markdown, and then render those posts as JSX elements. This allows me to add custom React components wherever I want inside a post. Here's how it works.

The berries to my pie

When I first started this blog, I was using .mdx files and using MDX to render the posts, but that didn't work for me for two reasons:

1. I wanted to practice fetching data from an API

2. I didn't like how I had to make a commit when I made a small change on one of my files.

So, I switched to using Contentful as a CMS. About halfway through the rebuild, I realized I wouldn't be able to use React components in any of my posts because I was rendering rich text from Contentful. I was crushed, but I learned a valuable lesson. Before making any changes to a project, make sure to identify the second-order effects of those changes. Failure is a waste if you don't learn something from it.

I also learned another valuable lesson: there is more than one way to skin a cat. Breaking MDX down to its first principles (as far as I understand it), it takes markdown content, renders it as JSX and sends that to the DOM. Simple enough. If I could write my posts through Contentful in markdown and then convert that to JSX somehow, I should still be able to add React components.

After some Googling, I found the perfect library: markdown-to-jsx. Here's an example of how I use it.

Here is some of the code for a SideNote component I made.

    const SideNote = (props) => {
return (
    <div className="mt-4 mb-5">
        <div className="text-md translate-y-6 font-mono font-bold text-slate-900 bg-slate-100 inline px-3 py-2 rounded-md ml-5 translate-y-6">{props.title}</div>
        <div className="-mt-1 text-slate-100 text-md font-mono bg-slate-700 pl-6 rounded py-3 border border-blue-300">{props.content}</div>
    </div>

)
    }

    export default SideNote;

Sometimes when I write, I want to add a side note but don't want to interrupt the flow of the text nor confuse the reader. To achieve this, I wanted something visually different and not just to write "Sidenote:'' followed by text that looks like the rest of the blog post. So I decided to make a component that takes a title and content as props.

When I'm writing a post and want to use it in Contentful, I can just write:

< SideNote title="Quick note'' content="Here's something else I want you to know." />

That will get displayed in the post like this:

Quick note
Here's something else I want you to know

That's a simple example, but it gives you an idea of the flexibility of writing content like this. Say I was writing documentation about how to do something with Framer Motion. Instead of just showing what the code looks like or linking to a Codepen, I can actually show you the contents.

Try tapping on one of these and see what happens.

Neat, huh! The possibilities are truly endless. To add that component to a post in Contentful, I just have to add it like I would directly in JSX. It looks like this

Screen Shot 2022-09-11 at 12.21.59 PM

I could have achieved this same functionality using .MDX posts, but again, I wanted to write in Contentful because the writing experience was better and I wanted to practice fetching API calls. Another benefit of this is that it allows anyone to edit React components once they're built.

I'm probably the only person who is going to write on my blog, but if I were using a team of writers who weren't developers, I could make the components and write simple documentation for them to actually use them. Email sign up forms, call outs, animations, you name it. I'm proud of what I've built here.

Speaking of which, here's my Subscribe component. Sign up to my email list of you enjoy new posts on technology, history, and science.

Layout

The home page is pretty simple. I have a PageTitle component that takes the title of the page and a description. It also has the option to take an image property because I use the GIF on the home page, but no other pages. I tried leaving the GIF on every page, but it was distracting from the rest of the content. It feels more special when it's left just on the /index page.

import Image from 'next/image';

function PageTitle({ title, description, image, width, height, small }) {
  const condition = image ? 'flex' : 'flex-none sm:flex';
  const secondary = small ? 'text-3xl' : 'text-5xl';

  return (
    <div className={`flex-row items-center ${condition}`}>
      <div className="basis-2/3">
        <div className={`${secondary} text-slate-300 font-bold mb-2 font-mono`}>
          {title}
        </div>
        <p className="text-slate-300">{description}</p>
      </div>
      {image ? (
        <div className="ml-2">
          <Image
            alt="Rotating gif of astronaut illustration"
            src={image}
            width={width || 175}
            height={height || 175}
            loading="lazy" />
        </div>
      ) : (
        null
      )}
    </div>
  );
}

export default PageTitle;

Then, I have a list of my latest posts called from Contentful. I limit this call to 8 so it only fetches my most recent posts. I then map through each post and render a PostListSimple component that takes the title, the slug, and the date it was created as props.

const PostListSimple = (props) => {

const {contentType} = props;
const slugFunction = () => {

    if (contentType === 'Book Notes') {
        return 'book-notes/';
    } else if (contentType === 'Aside') {
        return 'thoughts/';
    } else if (contentType === "til") {
        return 'til/';
    } else if (contentType === 'Essay') {
        return 'essays/';
   }
}

return (
        <div>
            <div className="max-w-lg text-blue-500 mb-1" key={props.key}>
                <LinkText slug={slugFunction() + props.slug} text={props.title} type="bright" />
            <span className="text-xs text-neutral-300 ml-1 hidden md:inline">{props.date.slice(0,10)}</span>
            </div>
        </div>
)}

export default PostListSimple;

Following all of that is my Subscribe component. The title and description of this component are props so I can specialize the call to action depending on what page it's on.  For example, if I added a subscribe element to a book notes post, I could say this "Subscribe for more book notes directly in your inbox" instead of just "Subscribe for more posts."

<div className="text-xl font-medium text-neutral-100 text-white -mb-5">
  {title}
</div>
<div className="text-sm text-neutral-300">
  {caption}
</div>

The rest of the site is pretty standard, except for the scrollbar. I'm simply using webkit to add custom CSS to the scrollbar.

I went back and forth multiple times on the color scheme because I love the minimal look, but I also want to showcase my skills with design and color theory. To be frank, I think most people can make a black and white blog with an accent color look good, so I wanted to challenge myself. The links were yellow at first, but something about blue links seems more professional to me--I can't quite put my finger on what that is.

Contentful

There wasn't much deliberate thought into choosing a CMS. I wanted something I could try for free and was quick to set up. Contentful satisfied both of those criteria. But I wouldn't recommend it. I've had trouble finding support and their docs aren't the most informative. Part of this could be the fact that I'm using a REST API instead of GraphQL though.

The biggest challenge I've had, that I've yet to conquer, is working with tags. Contentful has tags built into content models, but they're difficult to query. This is a problem I've been pondering since I've started. I shall make progress one day.

Since I couldn't query tags, I used a band-aid solution, but it works great. Essentially, I have a few different content models that are identical: Essays, Asides, Book Notes, and Today I Learned.

For the /index page, I query all the posts in my Contentful space and limit the results to 8.

export const getAllPostsFromContentful = async (limit) => {
    const client = createClient({
        space: space,
        accessToken: accessToken,
    })
    const posts = await client.getEntries({ limit: {limit} })

    return {
        props: {
            posts: posts.items
        },
        revalidate: 1
    }
}

For the /writing page, I query the Essays and Asides models and filter through those results, displaying a PostList component. I have a prop that's conditionally rendered to display an Essay tag or an Aside tag to differentiate the two. As you could imagine, essays are longer posts and asides are short Essays I want to share.

const Writing = ({ posts }) => {
  const writing = posts.filter(post => post.sys.contentType.sys.id === "Essay" || post.sys.contentType.sys.id === "Aside")

    return (
      <>
        <Meta title="Writing" description="A codex of my personal journey to understand the world" />
        <PageTitle title="Writing" description=""      />
        <div className="flex flex-row gap-6">
          <div className=''>
            {writing.map((writing, index) => (
                  <PostList title={writing.fields.title} description={documentToReactComponents(writing.fields.description)} date={writing.sys.createdAt} slug={writing.fields.slug} key={writing.sys.id} contentType={writing.sys.contentType.sys.id}/>
                ))}
          </div>
        </div>
      </>
    )
}

export default Writing;

I follow this same format for the /notes page and the /til page.

What's next

There are a lot of things I can keep improving, like figuring out a proper tags query and cleaning the code up, but I'm hesitant to continue optimizing it. For now, it's just a personal blog that I wanted to take pride in building and I wanted to have a space to share my writing that looks clean. I'm afraid continued optimization will prevent me from working on bigger projects where I learn more things.

Speaking of which, I'm looking for more things to build and continue expanding my abilities. If you have ideas or need help with something, reach out on Twitter!