New Website, First Post

Hello There/World!

This is probably the developer equivalent of the influencer "so a lot of you have been asking about my...", and given no one, literally no one, asked - here's my first post, about the build of the website and why.

Blogging about building a blog is very meta, a perennial developer classic and the comic below is so apt:

Comic charting blog infrastructure complexity vs content

Comic courtesy of: https://rakhim.org/honestly-undefined/19/ - (With this first, and currently only post, given the tech stack of this blog, I am definitively in the "Gatsby Fan" section)

Thought I'd divide the post in 2 main parts and let you, the reader, choose which parts to focus on, rather than tricking you into reading my life story when you are just looking for a JS snippet to get a feature working. With the actual build/features, there was a number of things of note that required some effort, and it wasn't a "5 minute job", so I thought I'd document and highlight these parts. Click here to skip to the technical stuff.


Non-technical:

Having a personal website has been on my to do list forever, and this current site is actually the last of several iterations that never even saw the light of day.

This is a shame as I have been helped out so many times in my career by people's personal websites and engineering blogs, reading production war-stories and pitfalls, tutorials and examples explaining nuances/tradeoffs not mentioned in the documentation of whatever is being written about. Whether it be helping to gain a much clearer understanding of a topic, or just a copy-paste of a code snippet that saves the day there are countless posts that have helped me do my job and improve as an engineer. Ironically, and guiltily, I have had many a time where I have found no resources or answers to a problem or question, subsequently worked said problem out and then not posted about the solution.

Why now?

For one, it is high time for me to address the previous point and secondly, writing docs or having to explain things in detail to others is one of the ways I find that really cements knowledge about a particular topic (the fear of publicly being wrong also ensures that you triple-check your understanding!). When explaining to someone else, there is no way of skipping understanding of a key concept with a "because" or "magic". However, given how deep down the rabbit hole one can go on almost any topic in software engineering, justifying or accepting that to yourself is very much ok, and sometimes just necessary to get things done.

In "Agile speak" publishing this site has been permanently blocked by a number of things:

  1. I had previously wanted to build a website building platform as a startup idea - therefore I obviously couldn't have my personal blog being built using something else.
    • Turns out this is quite an undertaking, and a little too ambitious for a one man project for the scope I was going for. It was similar in concept to AirBnb's Server driven design with the idea people could create their own "components" visually rather than writing custom CSS.
    • The pivot to my current project of algotrading put the brakes on it before MVP of the idea (and a hosted site) was completed.
  2. As a former frontend focused developer, I always felt that I had to have a flawless bespoke site, with a perfect Lighthouse score, and that using a theme/basic UI library would demonstrate a lack of skills.
    • There are some seriously amazing frontend dev portfolio/personal blog sites.
    • Therefore no site is better than a bad site (this is a bad mindset!)
    • Little bits of polish do take a significant amount of time, that is a reason why frontend developers have full-time jobs!
  3. Cutting scope and making it too bare-bones would not have features I wanted (eg: Tags/SEO/Open Graph Images).
  4. Wanting to use the latest and greatest, or "flavour of the month" tech stack to power the blog.
    • This has the problem of the goalpost of getting the first bit of content published being constantly moved. Not finishing and coming back to it later amplifies this problem as the latest and greatest never stays static.
  5. Chicken and egg problem with content/site.
    • Spent a long time drafting and not quite finishing multiple blog posts; knowing I then had to also finish the site to publish lowered my motivation to properly complete the posts.
    • Some features only make sense with content existing (eg: Sitemap, RSS feed, Tags, Newsletter subscription).
  6. Writing itself can be challenging to condense and explain concisely for some of these complex engineering topics; most of the tools have documentation spanning hundreds of pages.
    • Worrying about being called out for being wrong and accidentally misleading people (when you think you have it understood).

I'm sure many others have encountered one, many, or all of the above (and probably other points) when thinking about starting their own self-hosted website or blog.

Given all these points, and never quite completing it meant that whenever I decided to put some time into this blog, I ended up rebuilding, rewriting or changing what I already had, rather than actually progressing much. Reading back some old draft posts, I found out that a lot of my drafted content was now very out of date or superseded by a newer thing. With regard to the actual site code, I would almost always find out theres a breaking update or new way of doing it since I last picked it up (Hello gatsby upgrades...).

Realizations

This time it's done. I really mean it.

The "MVP" ideology is always used and easy to throw around, however the definition of "minimal" also depends on what you really want - I could for example, think the most minimal way of getting content online would be to post on something like Medium (which I have done years ago), however there are a lot of "minimal" features that are foregone (a little bit of personal/custom custom styling and branding, SEO, public access, extensibility to name a few).

These are some points I've had to remind myself as I'm getting the site and this post over the line:

  1. Everything really is iterative, "perfection" cannot remotely be achieved from the outset.
    • However there are so many items on a checklist that one could consider being required
    • Once started, adding more posts will be order of magnitudes less effort
    • Can always refine when the site is running
  2. Compound growth is a powerful force
  3. Things I say I would write about (problems I encounter) I end up not coming back to after I'm done with it
    • Completely out of date
    • I forget the full details of the problem
    • I forget the full details of the solution to the problem
  4. I keep writing tiny drafts/bookmarking things in Notion, but never end up doing anything about it because there was no "end result" and cumulatively wasting time and effort.
  5. Having to formalize thoughts/explanations helps massively in cementing knowledge and understanding of a topic.
  6. Starting in general is easy, finishing is hard.
  7. For something like a personal website/blog with largely text content, it is unlikely for the site itself to be a masterclass in pushing the limits of any current technology, and to convey the technical prowess of the creator. Instead it is the content within that does that, just like any CV really. This is easily demonstrated on some of my favourite posts, such as LMAX disruptor by Martin Fowler.
  8. To actually improve writing, one needs to actually write, and not just jot down notes.

Technical

Given the above narrative, the aim was to keep the website (relatively) simple and get it out the door. That being said, it wasn't a 5-minute job.

Requirements

  • Mobile responsive (yes thats a given, but when you are building things like a navbar from scratch it can be tedious!)
  • Tags/Categories for posts - I had a lot of various topic drafts and series of posts in Google Docs already, and wanted to group them.
  • Very easy to post/update
  • Cheap (I have learned the hard way from over-engineering and hosting tiny traffic sites on Kubernetes in Google Cloud... story for another day)
  • Using libraries/languages I'm already familiar with
    • Not WordPress
  • Quick for me to get up and running from zero, but not too complete that I need to start by tearing down rather than building up.
  • Be feature rich enough to be happy with it
  • Blazing 🔥 fast. Obviously.

Choices

If you read the first part of the post, you'll know that building large parts from scratch is a no-no for now. I wanted to fulfil all the requirements with the closest to "turn-key" as you can get. I ended up using the following as the core components:

Reasoning

Gatsby - It was really between Gatsby and NextJS to try and get the most "batteries included" framework with a large ecosystem of plugins and existing packages. It also has a number of examples/starter themes to look at to find how some functionality is achieved or fork and start with a functional site from the first commit.

I also knew it had to be static and I wasn't going to be spinning up a server/hosting this. I ironically have a drafted post in my abandoned posts graveyard about running a frankenstein Wordpress on K8s on GCP using persistent volumes and FTP that I actually use for other sites...

Gatsby

I find sometimes the most helpful thing for learning sometimes is to see a complex working example and edit it/pick it apart. You get the satisfaction of immediate progress that helps get the ball rolling. In the case of a personal website, forking an example, importing your content, and editing as you go is much more motivating (especially seeing your real posts rather than "lorem ipsum").

It isn't plain sailing though - Gatsby can be a pain when you want to go "off piste" and do more than edit some text in an example starter theme, and you will need to know how it works in order to be effective when implementing something custom. There are also a number of features I mention later that I feel probably should be built in.

One thing I didn't find recommended much anywhere was to ignore the "pages" folder and create all your pages in gatsby-node.js. This gives you way more flexibility and lets you use dynamic context in the queries.

Variable content builds

On the topic of dynamic page queries, being able to use environment variables to show different posts during different builds is one thing that is an essential feature in my eyes, and is available when logged in as an admin in most CMS systems. For a static site generator it is a little different, but the requirement is: draft posts in development whilst I'm writing/developing, but only the published ones viewable in a production build.

The way I did this is as follows. First you need to add various MDX keys that you are going to "filter by" to your posts.

---
title: New Website, First Post
type: blogPost
status: [draft]
date: "2022-02-21"
slug: "/post/first-post"
layout: blog
tags: ["Javascript", "React", "Frontend"]
excerpt: "..."
---
# Post content

In this case the only relevant line for filtering is the status key within the Frontmatter metadata section of the markdown file.

// gatsby-node.js
const getPublishStatus = () => {
    switch (process.env.NODE_ENV) {
        case "production":
            return ["published"];
        default:
            return ["published", "draft"];
    }
};

const publishStatus = getPublishStatus();

exports.createPages = async ({ graphql, actions, reporter }) => {
    const { createPage } = actions;
    const result = await graphql(
        `
            query ($status: [String]) {
                pages: allMdx(filter: { frontmatter: { type: { eq: "page" }, status: { in: $status } } }) {
                    nodes {
                        frontmatter {
                            slug
                        }
                    }
                }
                blogPosts: allMdx(filter: { frontmatter: { type: { eq: "blogPost" }, status: { in: $status } } }) {
                    nodes {
                        frontmatter {
                            slug
                        }
                    }
                }
                tags: allMdx(filter: { frontmatter: { type: { eq: "blogPost" }, status: { in: $status } } }) {
                    group(field: frontmatter___tags) {
                        fieldValue
                        totalCount
                    }
                }
            }
        `,
        {
            status: publishStatus,
        }
    );
    // ...
}

We then filter with status: { in: $status } } within the GraphQL query, using the publishStatus result, switching based on the NODE_ENV environment variable at build time. By default this will be "production" when running gatsby build, and "development" when running gatsby develop.

This then allows you to create the pages with their respective layouts below like so:

   let pages = result.data.pages.nodes;

    pages.forEach((node) => {
        createPage({
            path: node.frontmatter.slug,
            component: path.resolve(`./src/templates/page.tsx`),
            context: {
                slug: node.frontmatter.slug,
            },
        });
    });

Quick tip: if you want to view on your phone, tablet or even another computer on the same network whilst developing, add --host=0.0.0.0 to your command to serve it:

{
    "develop": "gatsby develop --host=0.0.0.0",
}

Now after writing a few posts in markdown, I noticed when linking to other posts within my site that they were full page loads rather than client side routing transitions. This is because they are built as standard <a href=""> tags rather than Gatsby <Link to="" />. I needed to add this plugin to make them work as you would expect.

Site features

Given I'm not sure what the target audience of this post is and its not really a tutorial, I'll just highlight some of the features I think are important, should be standard but not necessarily highlighted, or just cool. I have added links to the resources I found about how to implement each feature in much better detail if you want to implement yourself. Many thanks to all the respective publishers for making it easier to get all the features all ticked off my list.

Dark mode

This is one of those features that seems super simple on the surface, but to get working properly and handle persistance/jarring UX was a little fiddly.

The simplest way and the way you will find on most tutorials at first is just using client side JS (usually just some React wrapper component with a state that toggles a className). However this has the following downsides depending on the implementation:

  • If choice is not persisted to browser via localstorage, each refresh will render the default theme.
  • If the choice is persisted, but different to the default, there will be a flicker on page load as the browser parses the HTML, renders the default, then rerenders as the Javascript code executes and changes the CSS styles.

The optimal solution is to use a render-blocking inline JS script to ensure the correct class name is applied before everything gets painted in the browser.

Two of the most useful posts I found on the topic, eloquently explaining in a lot more detail than in this post are these two:

My setup is a little simpler than documented in those posts because the CSS framework I am using toggles dark mode with just a className rather than CSS variables, but still leverages most of the implementation details. All the various snippets from my more basic implementation are pasted below to use as desired.

// components/theme-context.tsx
import React from "react";

export const ThemeContext = React.createContext({
    darkModeTheme: "dark",
    toggleDarkMode: () => {},
});

export const useDarkMode = (): [string, () => void, boolean] => {
    const [theme, setTheme] = React.useState("");
    const [componentMounted, setComponentMounted] = React.useState(false);
    const setMode = (mode: string) => {
        window.localStorage.setItem("theme", mode);
        setTheme(mode);
        // "bp4-dark" is the dark mode css class used in BlueprintJS https://blueprintjs.com/docs/#core/typography.dark-theme
        // I also set the className directly as it is the only one on my <body>
        document.body.className = mode === "dark" ? "bp4-dark" : "";
    };

    const toggleTheme = () => {
        if (theme === "light") {
            setMode("dark");
        } else {
            setMode("light");
        }
    };

    React.useEffect(() => {
        const localTheme = window.localStorage.getItem("theme");

        if (!localTheme) {
            if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
                setMode("dark");
            } else {
                setMode("light");
            }
        } else {
            setMode(localTheme);
        }

        setComponentMounted(true);
    }, [componentMounted]);

    return [theme, toggleTheme, componentMounted];
};

export const ThemeProvider = ({ children }) => {
    const [darkModeTheme, toggleDarkMode] = useDarkMode();
    return <ThemeContext.Provider value={{ darkModeTheme, toggleDarkMode }}>{children}</ThemeContext.Provider>;
};

The main app.tsx is wrapped in the above <ThemeProvider>

// src/app.tsx
import React from "react";
import { ThemeProvider } from "./theme-context";

export const App = ({ children }) => <ThemeProvider>{children}</ThemeProvider>;
export default App;
// src/html.js
import React from "react";

export default function HTML(props) {
    return (
        <html {...props.htmlAttributes}>
            <head>
                <meta charSet="utf-8" />
                <meta httpEquiv="x-ua-compatible" content="ie=edge" />
                <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
                {props.headComponents}
            </head>
            <body {...props.bodyAttributes}>
                <script
                    key="dark-mode"
                    dangerouslySetInnerHTML={{
                        __html: `(function() {
                          try {
                            const localTheme = window.localStorage.getItem("theme");

                            const setMode = (mode) => {
                                window.localStorage.setItem("theme", mode);
                                document.body.className = mode === "dark" ? "bp4-dark" : "";
                            };

                            if (!localTheme) {
                                if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
                                    setMode("dark");
                                } else {
                                    setMode("light");
                                }
                            } else {
                                setMode(localTheme);
                            }
                          } catch (e) {
                            // Cowboy error handling
                          }
                        })();
                    `,
                    }}
                />

                {props.preBodyComponents}
                <div key={`body`} id="___gatsby" dangerouslySetInnerHTML={{ __html: props.body }} />
                {props.postBodyComponents}
            </body>
        </html>
    );
}

And then for the actual toggling button:

export const NavBar = () => {
    const state = React.useContext(ThemeContext);

    return (
        // Rest of navbar/layout
        <Button aria-label="Toggle Dark Mode" className="bp4-minimal" icon={state.darkModeTheme === "dark" ? "flash" : "moon"} onClick={state.toggleDarkMode} />
        // ...
    )
}

Table of contents sidebar

This is a neat little feature I find useful on a number of sites, especially ones that are heavy in text content. Being able to jump up and down a page to the point that is needed without using Command-F and looking for a word, or referring back to a section without having to hop through pages is a feature I wanted for my site. I also find the integration with the markdown headers also helps structure the content when writing somewhat.

As with most cool features for websites, a number of people have already written very detailed guides I used when making the site:

CSS grid layout

Now, although not a feature that m(any) visitors will even notice the implementation details of, I wanted to recreate the "holy grail" layout using CSS grid.

Josh W Comeau has another fantastic blog post explaining exactly how to achieve this.

My layout is a little simpler, and I did change the grid-template-columns code (below) to be responsive down to mobile like so:

.wrapper {
    display: grid;
    grid-template-columns: minmax(auto, 1fr) minmax(auto, 1000px) minmax(auto, 1fr);
    width: 100%;
}

Site map

Quick and easy, and not really thought of much day-to-day as it is a "set and forget" feature. Submit to webmaster tools to aid indexing of your site, and also to validate urls. I had some pages set to noindex that I would have been waiting for ever in Google results had I not checked and seen them reported in Google Search Console.

This should probably just be a default feature really, I can't think of any downsides for a public facing website. If you haven't already added it to your Gatsby site, it is a one-liner in your gatsby-config.js.

RSS Feed

Now I know some people still love their RSS and are still sad from Google Reader being dropped nearly 10 years ago. Its not a hard feature to set up, just a plugin and adding some links helps a few people, and the functionality actually lends itself to being able to have the next feature on the list.

Here is my config for the Gatsby RSS plugin entry in gatsby-config.js - It is very similar to the example in the documentation.

// gatsby-config.js
{
    resolve: `gatsby-plugin-feed`,
    options: {
        query: `
        {
            site {
            siteMetadata {
                title
                description
                siteUrl
                site_url: siteUrl
            }
            }
        }
        `,
        feeds: [
            {
                serialize: ({ query: { site, allMdx } }) => {
                    return allMdx.nodes.map((node) => {
                        const { excerpt, frontmatter, html } = node;
                        const { slug, isoDate } = frontmatter;

                        return Object.assign({}, frontmatter, {
                            description: excerpt,
                            date: isoDate,
                            url: site.siteMetadata.siteUrl + slug,
                            guid: site.siteMetadata.siteUrl + slug,
                            custom_elements: [{ "content:encoded": html }],
                        });
                    });
                },
                query: `
            {
                allMdx(sort: { fields: [frontmatter___date], order: DESC }, filter: { frontmatter: { status: { in: ["published"] }, type: { eq: "blogPost" } } })  {
                    nodes {
                        html
                        id
                        excerpt(pruneLength: 250)
                        frontmatter {
                            title
                            slug
                            tags
                            date(formatString: "DD MMMM YYYY")
                            relativeDate: date(fromNow: true)
                            isoDate: date(formatString: "YYYY-MM-DDTHH:mm:ssZ")
                            excerpt
                        }
                        fields {
                            readingTime {
                                text
                            }
                        }
                    }
                }
            }
            `,
                output: "/rss.xml",
                title: "James Collins' personal blog RSS feed, mostly about software engineering",
            },
        ],
    },
},

Github profile latest posts

This is a cool little feature I haven't seen that widely used yet which is the Github custom profile page that shows at the top of your profile when utilized, above than top repositories section:

![Github special user Readme repository]

I then found that some developers have been very creative and done all number of things with them.

One of the features I liked the most was the functionality to have auto updating links to recent blog posts. It makes sense, as people are much more likely to find relevant or want to read my technical blog posts if they find me via Github.

The basic summary is that a Github action reads the RSS feed of my site on an interval, and then with some parsing, updates and commits a new README.md with a number of most recent posts.

This post from Christoph Werner explains in detail how it's done:

Open graph images

I knew from the start that this site was not going to be photo-heavy in the slightest, but having open graph images appear when sharing urls in various apps (Slack comes to mind) is one of those really nice touches I wanted for my site.

Generating them manually for each post and again if I make changes, is not really a viable option, but luckily it can be done. My first idea was to render some small react component, send to Puppeteer and then save the screenshot as an image (like I have done before for visually unit testing components). This would have been some effort to implement, and luckily I found this approach, which I particularly liked, after searching a little (and the underlying image generation is in Rust, which is a cool bonus).

Something I didn't realise until I started testing on the live site is that, unlike most assets, Open Graph image tags cannot be relative paths. I was testing this site on a Netlify feature branch environment and still wanted the images to resolve correctly, so to get this working I did the following after reading the netlify docs for deploy urls:

// gatsby-config.js
const getSiteUrl = () => {
    switch (process.env.CONTEXT) {
        case "deploy-preview":
        case "branch-deploy":
            return process.env.DEPLOY_PRIME_URL;
        case "production":
            return process.env.URL;
        default:
            return `https://www.jracollins.com`;
    }
};

module.exports = {
    siteMetadata: {
        siteUrl: getSiteUrl(),
        ...,
    },
    ...,
}

Conclusion

Even though it is all finished now, I still feel like it could be refined or put down, just for a little while longer to come back to and polish up a little more before publishing to the world.

Things like:

  • The design could be significantly improved
  • I am missing features like an email newsletter sign up
  • There could be a search bar, or suggested posts at the bottom of each post
  • I could add better social sharing links
  • I'm sure theres 1 or 2 points of lighthouse score still to get.
  • And many more...

But these are tasks for another day (and blog post!)

TL:DR

Congratulations if you made it all the way through; this website has been very overdue (multiple new-years resolutions kind of overdue) and I have finally gotten round to actually getting the ball rolling. Improvements and content will be an iterative process. Hope you found the content interesting/helpful - more coming soon!