Building a microsite using GraphQL, CraftCMS and 11ty

Published on

One Sunday I was having breakfast with a friend and got talking about team structures and how projects vary. It was a really useful discussion and I felt that other people might find conversations like that useful too, this idea very quickly became Interviews by Shane Hudson. I began building it that day and within just over a week I published the website with five interviews.

In this post I want to explain some of my thought processes. I have worked on so many side projects that never got off the ground due to being over-engineered or just took too much time to make, so I knew I wanted to take a simple approach. I also try to always learn or practise something every time I make a side project, that way even if no one ever sees it it is still useful to me. These two principles drove the choices behind building the site.

Choosing the "stack"

The first step was to decide if the interviews should go in a category on my personal site or if it should be a thing of its own. Originally I thought about getting a domain for it to be its own site but if there is anything I've learned over the years it is that having lots of domain names is expensive to maintain. So that left me thinking it should go on my site but I felt it would be nice to keep them separate so the focus is clearly on the interviews and it would give me the opportunity to play around with the design and building it.

Now I had my excuse, a project that required it's own site and some content. For a while now I've been, mainly due to Andy Bell, interested in trying 11ty. It is a static site generator written in JavaScript. This means you can have layout files and content files that are built to HTML files. It also means you can have a lot of control of how that happens using JavaScript, particularly with where the content comes from or how it is parsed.

Okay, so that sounds doable... but I already have a CMS on my personal site, so I started thinking maybe this was all a mistake and I was once again over-engineering a side project. Then I had a brilliant idea, I had already paid the $59 for the CraftCMS GraphQL plugin (CraftQL) a while back when I made my Bookmark Uploader. After all, one of 11ty's biggest advantages is you can write JavaScript to access a data source. So that's what I did, I decided to hook 11ty up to my personal site using GraphQL so it was essentially a microsite that is fast and self contained but uses my existing CMS as a data source so I don't have content all over the place.

Getting started with 11ty

I had never built a site using 11ty before this. To be quite honest, I don't recall using a static site generator at all before this (except Nuxt but I usually use a server with that). The docs for it are alright and with open source projects such as Andy's Hylia, I was able to quite quickly get up and running.

When I started building it I realised I had not yet thought of a name for the site, so I just titled the folder "interviews-site" and carried on.

Fetching content

My first task was to get content on the site. I haven't worked a great deal with GraphQL so I got a bit stuck when I realised most of the tools I could find on npm seemed to work differently to how the CraftQL docs worked. Thankfully I found that apollo-fetch worked in the exact same way I wanted, so I could just write the query as a template string (so it has line breaks) and it returned a promise that I could do whatever I wanted with. The issue is that apollo-fetch is deprecated so I probably ought to find an alternative at some point.

Also of course I needed to decide how to store the content in the CMS. As I use CraftCMS I decided to add it as its own section so the content on the interviews site and on my personal site would never mix unless I specifically wanted them to. One of my most successful side projects in the past was Success Circuit, a site I started when I was 16 to interview people of all kinds. So I have experience writing interviews and know that one of the worst things I did then was store an interview as a blog post. Why? Because whenever I came to redesign, I never had the ability to know which part was the interviewer or the interviewee. I faked this using classes etc but for this project I wanted a way to properly separate them.

So I went the route of making separate fields, one for interview and one for interviewee. They both currently allow the same type of content but I know explicitly which is which.

Another thing I did, learning from my wish list for Success Circuit, was add data fields. Currently I've only got two: team size and role. The reason for this is these things are always mentioned in the interviews but there would be no way to know for example the split between interviews with designers or developers without going through each one individually. Now it is in a position where pretty much day 1 I was able to play around with the idea of graphs visualising the archive of interviews in a way that would be far harder without these fields.

const { createApolloFetch } = require('apollo-fetch')

const fetch = createApolloFetch({
    uri: 'GRAPHQL_API_URL',
    responseType: 'json'
})

fetch.use(({ request, options }, next) => {
    if (!options.headers) {
      options.headers = {};  // Create the headers object if needed.
    }
    options.headers['authorization'] = 'BASIC_AUTH_TOKEN'
  
    next()
})

module.exports = async function () {
    var statuses = process.env.NODE_ENV == "production" ? "enabled" : "disabled, enabled"

    return fetch({
        query: `{
          entries(section:[interviews], status: "${statuses}") {
            ...on InterviewsInterview {
              title, 
              slug,
              postDate @date(as: "Y-m-d"),
              body {
                content
              },
              teamSize,
              role,
              twoSidedInterview {
                ... on TwoSidedInterviewInterviewer {
                  __typename
                  text
                }
                ... on TwoSidedInterviewInterviewee {
                  __typename
                  text
                }
              }
            }
          }
        }`,
    }).then(res => {
        return res.data.entries || []
    }).catch(res => {
        console.error(res)
    })
}

Don't forget the homepage

Now I had everything I needed to make the interview layout as well as the archive page but I soon realised I needed content for the homepage. Ah. I had a few options. I could put the homepage content directly into the site, but that goes against the point of using the CMS. I could have created a new type, maybe "interviews pages" so they didn't show in the interviews section. But still that seemed messy and I probably only wanted one or two pages.

In the end I went with the option of using a "global" in CraftCMS that I titled Interviews and has a single text editor as its field. This worked well, it was fairly tidy (didn't need a section of its own) and I can easily edit it from the CMS. I did have to make a new data source to do this, but I essentially duplicated the file I used to get the interviews, renamed it globals and changed the query to get the global instead.

At this point I required a different layout in 11ty, that was really nice to do as I needed to change the layout at the top of the index file to a new name and have a matching file in the layouts folder. Another thing I needed to do is due to having links in the homepage content was use a markdown filter so the content from the CMS came through as HTML instead of plain text.

Drafts

You may notice the code above has a variable called statuses, this was something I left till the last minute. Before the website was published, it didn't matter if the interviews were "enabled" or "disabled" (published or draft) in the CMS because they were not being shown anywhere. So I could use the GraphQL query without checking if published. But once I published the site, I would have to keep disabling any I didn't want to publish before I built it. This quickly became annoying.

So I wrote a very naive way to manage it. If the node environment was set to production then it would show only published interviews, otherwise it shows all of them whether published or not. This seems to work fine and reduces the hassle. If I want to see what it would look like on live, I can run the build command with the production environment variable.

Hosting

I mentioned production, so I should probably explain my hosting set up. Part of my plan for choosing 11ty was so I could easily host it anywhere, it is just HTML files. I know a lot of people use Netlify or Github Pages. However, while I can host it anywhere... I currently host it on a big server that I use for everything else. So why does it matter? My friend Dave once said I am like a cat at the door, always wanting the option to go out without actually ever doing so. I currently have a server and can easily host it, but one day maybe I will want to move that stuff somewhere else. The smaller and easier it is to move, the more likely I will or at least gives me the option instead of being stuck by decisions I made in the past.

Currently my setup is a server running Dokku. It is just like Heroku but self-hosted. So I have a git repository that I push to Dokku and it sorts out all the deployment and gives me a URL. This can sometimes be a temporary one, but in this case I used a subdomain of my personal site. As it was my first time using a static site generator I was wondering whether to only push my generated files or the whole project.

What I ended up doing was pushing the generated files (with a .static file so Dokku knew it just needed nginx and nothing else) but I also set up a script on the server that will pull down the full project from a separate git repository and build it. So now if I want to deploy the site on the fly, I can ssh from my phone to my server and run the deploy script. Now, I'm aware this sounds silly but it is much better than requiring I have access to my laptop to build it and push whenever I update content. The ideal solution that I may look at in the future is to run the deploy script either hourly on a cron job or every time I save an interview in the CMS.

Analytics

Ok so by this point the setup is working pretty well. But I figured after publishing that it was a shame I had no idea how many people had been on the site! I didn't really want to use Google Analytics, I am trying to avoid using them when possible and I really liked that I have zero JavaScript on the front end. The site is generated using JavaScript but the user doesn't have to run any at all. That's nice. So what I wanted for analytics was something like the old AWStats but I wanted something I hadn't used before. I ended up finding GoAccess and that was really nice for what I need.

I had a bit of trouble setting up the analytics. As it doesn't use JavaScript, it has to have access to the log files from nginix. The issue was as I used Dokku, these were locked away in a Docker container that was remade every time the site was deployed.

Thankfully I was able to use Docker volumes (Dokku calls it Storage) which meant I could have a persistent folder that each Docker container used regardless of the site being redeployed and it let me have access to that folder outside of the container. So in the end what I did was run GoAccess on a cron job hourly so that it updated another persistent folder with a static output of the analytics, that folder exists within the site so now the analytics are publicly accessible at https://interviews.shanehudson.net/analytics/. I've never made analytics public before but I figured why not, there is no private data and it makes it easy for me to see without having to log in or anything.

Design

I'm not a designer, that's clear to anyone that has visited my personal site or any side project. But I have worked with many brilliant designers and would have expected to have picked up a trick or two. If rewind to the point where I had got the content coming through from the CMS, technically by that point it was a fully functioning website but it had no design!

Shocking

I was looking at this black and white website trying to figure out what to do. Had I made an awful mistake? It's hard enough making one website for yourself let alone two. I began wondering if I should stop while I was ahead and just publish the content on my personal site.

Recently I read a book called Art Direction for the Web by Andy Clarke. I found it to be a brilliant book to read. It put all the fundamentals together in a way that had never really clicked before in my head. So I figured I would use what I learned from that as inspiration. I quickly knocked together a grid layout that felt simple but strong. I chose a nice blue background and Helvetica Nueue. It looked fine. But it didn't stand out.

I've never been very bold with design, but I've been boring myself so I figured why not try to be bolder. We've been painting our house quite bold colours and I love it. Thinking about that reminded me of something in Andy's book about shock factor. He mentioned it in his video for Smashing Magazine too, if something wants to stand out then make it shocking. So I took that idea and ran with it. I kept the site as simple as possible but wanted it to shock.

So I changed that blue to green. I then later after publishing improved the green slightly, it was a bit too shocking! Now it is just right, shocking but readable. I also tripled all the padding/margin spacing and made the borders 10 times thicker. Fonts all became bold. That worked. That got the website to a design that was very simple but bold enough to stick in people's minds.

Twitter Cards

Okay so this is probably me jumping the gun a little but one thing I've never liked about my site or any of my side projects is the lack of nice Twitter cards when I post a link. Of course I say Twitter because I'm addicted to it, but it works on Slack and Facebook too.

I remembered seeing a post about using serverless functions to generate a card on the fly. I realised that was not something I wanted to do, but it had potential... so I wondered how I could generate the images when the site is generated.

I've not quite got to that point yet but I have made a little page that looks much like the site but just has the heading in very big font. Currently my process is to update the heading with the number of the interview manually then take a screenshot of the element. This should be easy to automate so plan to do that at some point. It is a sleek process because by keeping the interviews anonymous I decided to just title them incremental numbers so I don't need to worry about fitting titles in etc.

When to publish?

As so many of us, I am notoriously bad for wanting things to be perfect before publishing any side project. I wanted this to be live as soon as possible or it never would be. So before I had any interviews ready, I put the site live with a placeholder post explaining interviews would come. That was before I had even written proper homepage content.

I had to decide whether to publish after the first interview or have a bunch ready. I decided a good amount would be a batch of 5 interviews on release. I don't know yet if I will always publish 5 at a time but I think it worked quite well and I encourage everyone to read them all. My intention is to occasionally write summaries comparing how different teams work based on these interviews.

It's such a simple little project but I'm quite please with how it's going! Please do have a read.