Introduction
As I switched my website to Analog, I had to make some decisions. I wanted to migrate every post, and most of the other text content. Analog adds some very nice quality of life features to Angular, which is already a powerful batteries-included front end framework. My previous portfolio, however, is built in WordPress, which provides a lot of template options for making sites like portfolios or blogs. Analog does not have a large template ecosystem at the moment, so that meant that I would need to build a lot of blog and portfolio functionality from scratch. I still wanted to use Analog, because the markdown and file routing support promise minimal friction if I just want to write a new post.
Planning
Once I knew what content I wanted to bring over and that I would have to build a lot of the site functionality myself, I looked at how my WordPress site is structured for inspiration and made lists for what features I wanted to keep vs discard:
- Keep
- Homepage
- About page
- Blog
- Blog index page
- Blog post page
- Categories
- Tags
- Previous/next post links
- Archives
- Filtering posts and route by year and year/month
- RSS
- Discard
- Accounts
- Comments
- Nice to haves for later
- Search (decided to implement a simple client side only version later)
This site is the canonical source for posts, but I currently repost them to dev.to, which has account and comment support.
New Features
In addition, I thought of new features I would want to have:
- Markdown support
- Photos page
- Simple service that reaches out to the Flickr API to get some of my photos
- Talks page
- Place to list any talks I have given
- Light/dark mode
- Set by system preference initially
- User toggle for light/dark mode - NYI
Styling
I decided to use Tailwind CSS, both to become more familiar with it and because it seemed like a good fit. The Analog create analog@latest
package template includes an option to generate a new project with Tailwind support from the start. It has been easy to build the site responsively with Tailwind, as it is designed around mobile-first layouts. I added the @tailwindcss/typography
plugin for some decent text styling defaults. The other default styles were useful, but I did customize the tailwind configuration slightly:
- Added a text shadow effect, which I adapted from this article
- Removed showing the back ticks when styling text with a monospace font
Tailwind is opinionated, but it isn't a full component framework. This site is simple enough that I didn't see the need to add any other CSS frameworks. I made UI elements from scratch, like the popover, which is built with content projection.
Data Handling
I decided early on that this site would only have a client. The main reasons to have a back-end would be to support things like accounts, which I don't think I need. Since the majority of the routes interact with markdown files in some manner, I decided to use front matter as a pseudo-api. The front matter on a given markdown file has the title, route slug, and date. injectContentFiles()
and injectContent()
effectively became my get()
and getById()
methods. I made the following interface to interact with the front matter:
export interface BlogPost {
author?: string;
category?: string;
cover_image?: string;
cover_image_author?: string;
cover_image_source?: string;
cover_image_title?: string;
date?: string;
description?: string;
last_updated?: string;
published?: boolean;
slug?: string;
tags?: string;
title?: string | null;
}
I did make a small API service for Flickr, where I host my photos. Flickr does have a public API for this purpose. If I could no longer rely on Flickr, I would need to come up with an alternate solution.
Other Priorities
Accessibility
Accessibility is really important, and like a lot of things in web development, it is much easier to implement from the beginning rather than with a complete project. Aside from the usual considerations, I made sure that the site is fully navigable from the keyboard. While styling the site, I also spent time considering the contrast for the light and dark themes, and the syntax theme for Prism. I found a set of prism themes that are a11y compliant. I also tweaked the wrapping on the code blocks after following this guide.
While the best accessibility testing comes using tools like screen readers directly, I have found these plug-ins very helpful:
-
WAVE Evaluation Tool
- Provides a simple audit of individual pages for accessibility concerns. Needs to be reloaded after navigating to a new route within a SPA. Available for Chrome, Firefox, and Edge.
-
Color Contrast Checker
- Shows if color contrast is sufficient, at different standard sizes. It can help developers have a consistent flow if checking contrast in multiple browsers.
SEO
I don't care a lot about SEO for this site, but I wanted to at least set meta tags including OG tags and have a sitemap. I made a set of utility methods to dynamically build sitemap links for things like posts, categories, and tags.
The official Open Graph tool is very useful to confirm that the OG tags are working as expected.
Building the Site
Since this is a personal project, I was okay with starting simple and seeing what my needs were. I don't think I'll need to implement NgRx in this project, for instance.
Routing
My WordPress site had a blog route structure of /:year/:month/:slug
. For this site, I decided to use /blog/:year/:month/:slug
. I wanted to be able to navigate to either /blog/:year
or /blog/:year/:month
and show posts that matched those parameters. The year and month are consumed from the date
property in the front matter, so that the posts could control the routing programmatically. Under the hood, slug and file name are still the real unique identifiers, so I couldn't have two files with the same slug and different dates without additional work. I would implement that for a client project, but I think that will be fine for this site.
Archive Component
I created a component that traverses the provided list of posts and creates an array of labels and routes for years and months that have posts.
Pills Component
The site supports both categories and tags for organizing content. I decided to create a generic pills component for both. The tags are saved as a comma separated string, so I split the string into an array.
Images
Between wanting to support cover images for blog posts, and showcase some of my photos, I knew I would need to support a lot of functionality for images.
Cover Images
In order to support cover images for posts, I added a number of properties to the front matter:
cover_image?: string;
cover_image_author?: string;
cover_image_source?: string;
cover_image_title?: string;
Some of these apply to the cover image directly, and I use some for the popover.
I used a version of this for the recent photo album component that I added to the home page.
Popover
For the post cover images, I wanted to provide an easy way to show the photo name, author, and link. I decided to use a popover for this. Since I needed to make one by hand, I decided to use content projection. I made a component with the responsibility of having the popover icon, that would open a passed in additional component as projected content. I set the tab index on the popover icon so keyboards could navigate to it. This worked out nicely, and so I added it to the photos in the masonry grid also.
Masonry Grid
I have used Flickr for many years, and I have always liked the way that masonry grids look. I wanted to put some of my favorite photos on a page, where they would randomly load from a list into a masonry grid. My initial approach involved a directive, which felt too heavy for what I needed. I looked around for a CSS only solution, and found this very useful article. I tweaked the styling and ended up with these rules.
Object Fit
After implementing the masonry grid, I styed the post and talk thumbnail images with object-fit: cover
to improve the flow of the cards.
Broken Image Directive
I wanted a solution to help prevent image link rot. I added a directive that listens to the onerror
event for the target element, which in this case is img
. If it fires the onerror
event, it replaces the broken image a fallback instead, which I host locally.
Draft Functionality
I wanted to be able to support draft functionality, where a post would be hidden from routing if it didn't have published: true
. Draft posts still support direct navigation. To support this, I had to add filters to the lists of posts, tags and categories, and the post navigation buttons. I also added a banner to a draft post to make it easier to distinguish.
RSS
One of the nice features that Analog supports is RSS. I wanted to continue to provide an RSS feed, which is something that comes with WordPress sites by default. Right now, I have a rough implementation that I plan on expanding in the future: feed.xml.ts
.
Static Site
As the same became usable, I started leaning toward building and deploying it as a static site. Static sites are very portable, which is important to me. I don't want to be stuck with a hosting service that I don't like because I can't easily switch. After evaluating the available options and my needs, I chose GitHub Pages for hosting. It is free for small scale sites, which this is. I was able to adapt the Nitro Github Action template to build and deploy. I configured it to deploy when I push to the main
branch.
There are limitations to static sites and hosting on GitHub Pages. If I wanted to have a back end, I would need to host it somewhere else. In order to make changes to the site, whether programmatic changes or adding a new post, I would need to commit to the GitHub repository. I don't have any kind of admin panel I could use to make changes from any web browser. I also need to build the site in order to deploy it. The build times are quick, but not instant.
For this site, none of these are deal breakers, but I wouldn't choose static sites for all use cases.
Gotchas
Initially, I used the standard /blog/:slug
route structure for individual posts. However, after I decided I wanted to preserve the /blog/:year/:month/:slug
structure, I ran into an issue with how Angular deals with rerendering a component if only a route parameter value changes. If I navigated from one route structure to another, like /blog
to /blog/:year/:month/:slug
, the routing and rerendering worked as expected. However, if I only changed the /blog/:year/:month/:slug
router params, like moving from one post to another using the navigation buttons, the route would update but the page would not rerender. This is intentional behavior in Angular. In order to get the behavior I wanted, I needed to add listeners to the route parameters.
Conclusion
Recreating my site in Analog took me a while because I had less free time to work on it than I liked and kept finding things to add, but I am very happy with the results so far. I still have some polish to apply, and some new features I want to implement, but I now have a site where it's easy for me to just publish a new post. The site is easy to move to new hosting if I need to, and it doesn't require a lot of dependencies or resources to build.
The post How I Switched My Website to Analog appeared first on Hapax Legomenon.
Top comments (0)