Hello again! This is a continuation of my previous article about picking the technology to power my site. If you’d like to catch up, read it here.

Today, I’ll share what I did to quickly get this site off the ground.

A quick review

The goals I had on my website were to share my knowledge, and to share my projects. To do so, I need to make sure my site is easy to write, easy to read, and easy to share.

I found that Astro was a good tool for the building my site and managing my content.

Building a template

There’s no need to re-do work that has already been done, so to speed up development, I started this project by starting from a template. The Astro command line setup wizard made this easy.

The Astro CLI

Using this tool, I was able to configure a project with little trouble. I followed the instructions here.

Since I use pnpm1 as my package manager, I used the following command in my terminal to start the setup.

pnpm create astro@latest

Following the prompts, I selected these options for building my project:

The next steps asked if I wanted to install my project’s dependencies and initialize git. I said “Yes” to both prompts. I was going to do those steps myself anyway, so it’s nice for the Astro CLI to take care of that for me.

After some waiting, everything was built and ready to go. I even got a fun message from Houston, Astro’s mascot, wishing me luck in my development!

Astro's mascot, Houston, wishing me luck on my project

The initial site

I navigated to my new project directory and started the development server to see what Astro generated for me:

cd my-astro-blog
pnpm run dev

And what I got was this:

The initial blog template's home page

For a starting template, this is pretty good. It contains a home page, some sample blog posts, and some images. It was a good collection of files that would likely be in a website.

Although I haven’t written any content yet, this is enough to get started on the next step: building and deploying the website.

Automating deployments

I wanted to automatically build and deploy my site whenever I make changes to the codebase. Configuring this as soon as possible saves me time, making it easy to share my updates. To make this very simple, I’m deploying my website using GitHub Pages.

Again, Astro’s documentation made this process very straightforward. The code to enable this feature was copied and pasted into my project, but I’ll explain the basics. The file’s included here for reference.

The deploy.yml file defines the configuration for automated deployments to GitHub Pages. The name defined in this file labels it as such:

name: Deploy to GitHub Pages

Changes will be made every time I push code to the main branch. This usually happens when I push code in another branch in my repository, and verify they look good in a merge request:

on:
  push:
    branches: [main]

The deployment is split into two jobs, build and deploy. These jobs are also broken down into individual steps:

jobs:
  build:
    # ...
    steps:
      # individual steps for build...

  deploy:
    needs: build # this step can't happen unless build completes
    # ...`
    steps:
      # individual steps for deploy...

Once these two steps are complete, my site will be online and available for all to see. With this configuration complete, I can focus exclusively on writing content and adding features to the site!

Gotcha: colliding deployments

I decided to re-name this file at one point, but accidentally forgot to remove a previous version of the file. Because I did this, I accidentally triggered multiple deployments whenever I pushed code to the main branch. This is wasteful, and also leads to confusing build output.

Consider this commit I made here. If you click on the build status icon next to the commit message, you’ll see that there were more build steps that were executed than expected. They’re highlighted in red in the image below.

Accidental duplicate deployment steps highlighted in red

Which jobs are the ones I actually care about? It’s difficult to say, but removing the duplicate files made things easier to manage.

Now that my deployments were taken care of, I began building off the blog template.

Although, as you can see in the image above, the original blog template is not bad. However, there were some things I wanted to add to improve it.

More accessibility

Although the blog template boasts a 100/100 Google Lighthouse2 score, it cannot detect all accessibility needs. Manual intervention is required to make sure everything is just right.

Keyboard navigation

For one, I want to allow me to navigate pages with my keyboard. This will make it easier for anyone who doesn’t want to use a mouse to move through the website.

I added a “Skip to content” button in the navigation bar, and a “Return to top” button in the footer, but I also wanted to navigate along different sections of a blog post easier.

Inspired by Astro’s documentation and in a few developer blogs, I wanted to add some links to the end of the headers. This provides a double benefit of allowing navigation with the Tab button, but also making sections of any blog post easier to share.

This is where the rehype-autolink-headings package comes in.

Remember that my blog posts are Markdown files. Astro converts Markdown files into HTML elements when it builds a site. Astro also supports plugins, which can modify the generated HTML even further.

I used the rehype-autolink-headings plugin to help me out. You can read more details about it here.

First, I installed the rehype-autolink-headings plugin.

pnpm install rehype-autolink-headings

Then I added the plugin to my Astro configuration file.

The Astro documentation says that if a plugin needs to define some configuration as input, the plugin and the configuration object should be grouped in an array, and then placed in the rehypePlugins array.

export default defineConfig({
  // ...
  markdown: {
    // ...
    rehypePlugins: [
      // ...
      // vv the plugin         vv the configuration
      [rehypeAutolinkHeadings, autolinkOptions],
    ],
  },
});

The autolinkOptions object is my configuration object, but what’s in it? The documentation for that object can be found here.

const autolinkOptions = {
  behavior: "append",
  // ...
  properties: {
    ariaHidden: true,
    className: "link-icon-container",
  },
};

At the end of this process, each header in my blog posts now have a link that is automatically appended to it. Now you can Tab to different parts of the article, and copy links to specific sections of the article. Give it a try right now, if you’d like!

Better screen reader support

I needed to manually verify that screen readers worked effectively. This is one of the things that Google Lighthouse cannot verify by itself.

The biggest catch was my navigation link to the résumé page. Here it is highlighted below:

Picture of my navigation link to the résumé page

Although “résumé” is a valid spelling of the word to describe my list of professional experience and education, screen readers would pronounce the link “resume,” as in “begin” or “start again.”

I assigned the aira-label property of the link to “résumé” by doing the following:

<HeaderLink aria-label="résumé" href="/resume">
  Resume
</HeaderLink>

Although this is a JSX component, and not a typical HTML <a> tag, this works because the HeaderLink passes the properties of the component directly to the link.

<a /* ... */ {...props}>
  <slot />
</a>

After Astro builds this component, the result looks like this:

<a href="/resume" aria-label="résumé" <!-- ... -->> Resume </a>

With these attributes assigned to these elements, screen readers will now read the link correctly, while keeping the appearance of the link that I like.

Changes like this remove any confusion from users who use screen readers to read my site.

Dark mode support

There’s no dark mode support in the initial Astro blog theme. Without a dark color scheme, readers (including me) may wince due to eye strain when reading my site in low light conditions. This is not a good experience.

I handled this primarily with CSS, and it gave me an opportunity to show off some of my personality. I had to pick the colors I would use to theme this site. I went with the gruvbox color theme, which matches the color scheme for my development environment!

First, I assigned a series of variables that matched the colors according to the palate. By default, the theme would be dark.

:root {
  --bg: #282828;
  --fg0: #fbf1c7;
  /* ... */
}

By assigning these variables to the :root pseudo-class, I can access them in any other stylesheets and components I create.

Once assigned, I could easily assign these colors to the elements that need them.

body {
  background: var(--bg);
  color: var(--fg0);
  /* ... */
}

I didn’t want to ignore users who preferred a light theme. Sometimes when reading in a brightly lit room, it’s easier to read a brighter page than a darker page.

Supporting a light mode after assigning my color variables was trivial, thanks to the prefers-color-shceme CSS media query. Depending on the settings defined in the user’s browser, a dark or light theme will be used.

Light mode support was added like this:

@media (prefers-color-scheme: light) {
  :root {
    --bg: #fbf1c7;
    --fg0: #282828;
    /* ... */
  }
}

And that’s it! The page now has a dark and a light theme, with some fancy colors as a bonus! Here’s what the new colors look like in my previous blog:

The home page with a dark color theme

And the same page with the light theme looks like this.

The home page with a light color theme

You can even compare the previous theme screenshot with the screenshots above. Which screenshot is the most pleasant to look at? Do any of them hurt your eyes? I’d like to know!

Conclusion

In short, I used Astro’s CLI to create a simple blog template, and enhanced it with some accessible changes to make it easy to read. I also automated my project to update my live site on changes, making the page easy to write and easy to share with readers.

Next, I’ll continue talking about building this site, but I’ll focus on some design changes I made compared to the original blog template. I’ll share how I think about the purpose of a product, and assess whether the design is aligned with its purpose. See you then!

Footnotes

  1. pnpm is the Performant Node Package Manager. ↩

  2. Google Lighthouse measures the accessibility of a web page in Chrome based web browsers. ↩