How I rebuilt my Angular blog and became an Astronaut
October 12, 2021 - 16 min readBuilding your own blogging platform is no small feat. From picking the stack you want to use, along with CSS solutions, to how you're going to organize your content are all the decisions that you have to make. I have gone down this path a few times with my own blog built with just Angular, and Angular and Scully, and hosted solutions like Dev.to. I continue to want something with low-friction so that I could get my thoughts out and be able to write blog posts more quickly about things that I've learned along the way or things that I'm tinkering with. This post is how I started on a different path, ended up rebuilding my blog and became an Astronaut 🚀.
TL;DR - GitHub Repo for my blog rebuilt with Astro.
Backstory
My friend and colleague Juri just finished a blog post series on building a blog with Next.JS, so I was all but ready to rebuild my Angular blog using the same stack. But as with all things in tech, when something else comes along the way we want to take a look and see what can be done with that also. Along my Twitter feed, I noticed a relatively new static site builder named Astro that piqued my interest.
Astro
Astro is a new and different type of approach to building a static site. It ships zero JavaScript by default, has its own notion of components, and one of the most interesting parts of it is that you can bring your own framework.
After reading the docs, I originally sought out to build an Astro renderer for Angular components. I made some progress there, but that’s a digression for another post. While reading the docs, most of what I needed to rebuild my relatively simple blog was supported by Astro, so I decided to try it out.
My Current Stack
Before getting started, this is what my current blog platform stack looks like:
- Angular - Frontend framework for web development
- Angular Component Router - My declarative router for Angular
- Firebase - Hosting solution for websites
- Prism - A JavaScript library that provides syntax highlighting for code snippets
- Prism Themes - Themes for syntax highlighting
- Reading Time - Calculates the minutes it takes to read the blog post
- Scully - A JAMstack static site builder for Angular apps w/Markdown support
- Tailwind CSS - A utility-first CSS framework
Currently, no interactive JavaScript functionality is present. Scully is the main tool behind the stack. At a high level, I build my Angular application first. Scully then takes the built Angular application and renders each page statically using Puppeteer underneath the hood.
So why switch? Mainly for two reasons.
- I wanted explore building the same site without shipping any JavaScript.
- I wanted to have a better workflow for building the components that make up the site without having to rebuild the application and content every time I make a change.
Now let's get started with Astro.
Getting Started with Astro
To create a new project with Astro, I followed the Quickstart from the docs to create a new project.
Open a terminal, create the directory for the project, and navigate into that directory.
mkdir astro-blog && cd astro-blog
Next, initialize the project with Astro
npm init astro
I chose the
Generic
site.
Install the project dependencies
npm install
Run the dev server:
npm run dev
After the site starts running, visit http://localhost:3000 in your browser to view the landing page.
Next, I jumped into making components.
Astro Components = HTML+BYOC (Bring Your Own Components)
Astro introduces its own component syntax using the .astro
extension. Astro’s Component Syntax is a special HTML-like file format which Astro uses for templating. It looks and feels very familiar to people familiar with HTML or JSX.
The first page I migrated was /blog
, which lists each blog post by title. I copied my markdown files into the src/pages/blog/posts
directory. I then created a src/components/Posts.astro
file to read the contents of those files to get the metadata from the Markdown file, or frontmatter. I then used the Astro.fetchContent()
method to read those files and list them in the content area below the code.
---
// Posts.astro
let allPosts = Astro.fetchContent('../pages/blog/posts/*.md');
allPosts = allPosts.sort((a, b) => new Date(b.publishedDate).valueOf() - new Date(a.publishedDate).valueOf());
const posts = allPosts.filter(post => post.published);
---
<div class="text-2xl w-4/5">
{posts.map(post => {
return (
<div class="py-4">
<a href={'/blog/posts/' + post.slug} class="text-gray-600">{post.title}</a>
<p class="text-sm">
{post.publishedDate}
</p>
</div>
);
})}
</div>
From the snippet above, Astro let’s you embed code directly into the .astro
file enclosed in a block of three dashes. This is where you can bring in JSX style templates, along with React/Svelte/Vue/Lit/etc components for use in the template area, and use JavaScript/TypeScript code directly. The one caveat is that there is no type-checking a compile time.
The area below the embedded code is your template. You can also embed style
and script
tags directly in the .astro
file.
The Posts.astro
file is now a reusable component I can use within a page.
Using Tailwind
Using Tailwind inside an Astro project is a matter of installing Tailwind, adding a Tailwind config, enabling the Tailwind config in the Astro config, and setting up a global css file to import it. I won’t rehash those instructions here, but you can reference them in the Astro docs for setting up Tailwind.
Routing
Astro uses file-based routing, which has really grown on me from seeing it in other React frameworks like Next.js, and Vue's Nuxt.js. To create a “route”, I created an index.astro
file inside the src/pages/blog
directory.
In the src/pages/blog/index.astro
file, I imported the Posts
component and drop it into the template.
---
// index.astro
import Posts from '../../components/Posts.astro';
---
<Posts />
No configuration to update, no components to map, just following folder and file conventions. My custom declarative router? Nowhere to be found 😢, but that’s ok. Now when I navigate to http://localhost:3000/blog, I see a listing of blog posts.
Any file within the src/pages
directory with an .astro
or .md
extension is treated as a route. It also supports static routes, dynamic routes, 404 routes, along with named and rest parameters.
For redirecting from /
to /blog
, I updated the src/pages/index.astro
to use a meta tag.
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>Brandon Roberts - Web Developer</title>
<meta http-equiv="refresh" content="0; URL=/blog" />
</head>
<body>
</body>
</html>
Using Layouts for Common Pages
The blog posts page is just a plain list of posts, and I will have other pages also, so next I needed to create a layout. A Layout in Astro is a special type of Component
to help you share and reuse common layouts within the project.
I created a src/layouts/BaseLayout.astro
file, and added some static HTML markup for structure
// BaseLayout.astro
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Brandon Roberts - Web Developer</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
name="description"
content="Brandon Roberts - Web Developer, Angular Team Member, NgRx Core Team, Open Source Project Maintainer"
/>
<meta
name="keywords"
content="Brandon Roberts, Blog, Web Development, Angular, HTML, CSS, JavaScript, TypeScript"
/>
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet"
/>
<link rel="stylesheet" href={Astro.resolve("../styles/global.css")} />
</head>
<body>
<main class="flex flex-grow justify-center min-h-screen p-4">
<slot/>
</main>
</body>
</html>
The BaseLayout
includes the global styles setup with Tailwind.
<link rel="stylesheet" href={Astro.resolve("../styles/global.css")} />
The Astro.resolve()
handles retrieving contents of files embedded in your templates.
I updated the src/pages/blog/index.astro
component to use the BaseLayout
.
---
// index.astro
import BaseLayout from '../../layouts/BaseLayout.astro';
import Posts from '../../components/Posts.astro';
---
<BaseLayout>
<Posts />
</BaseLayout>
I also needed a layout for each blog post, so I created a src/layouts/BlogPostLayout.astro
with similar HTML structure for the blog posts, along with the title of the blog post, and the date it was published.
---
// BlogPostLayout.astro
const { content } = Astro.props;
---
<html lang="en">
<head>
<title>{content.title}</title>
<!-- excluded for brevity -->
<link rel="stylesheet" href={Astro.resolve("../styles/global.css")} />
<link rel="stylesheet" href={Astro.resolve("../themes/prism-atom-dark.css")} />
</head>
<body>
<main>
<div class="flex flex-grow justify-center min-h-screen">
<article class="w-screen max-w-4xl p-8">
<h2 class="text-gray-600 text-2xl">{content.title}</h2>
<span class="font-light text-sm">
{content.publishedDate}
</span>
<slot/>
</article>
</div>
</main>
</body>
</html>
Things to note:
- The Astro object is global and provides information about the page, provided props, and so on.
- In order for the syntax highlighting from Prism to work, I copied the theme into the
src/themes
directory and referenced it in theBlogPostLayout
markup. - The
<slot/>
is used to render the content from the markdown inside the layout.
Now that I have layouts for pages and posts, I needed to render the blog post content.
Using Markdown for page content
In Astro, markdown files are treated as plain text, and converted to HTML using remark with syntax highlighting done using Prism pre-enabled. In order to use the BlogPostLayout.astro
in my markdown files, I had to add the layout
property to the frontmatter in the markdown file. One example file looks like this:
// 2021-10-12-how-i-rebuilt-my-angular-blog-and-became-an-astronaut.md
---
title: 'How I rebuilt my Angular blog and became an Astronaut'
description: 'I was ready to rebuild my blog with Next.JS
but then decided to take another approach'
published: true
slug: 2021-10-12-how-i-rebuilt-my-angular-blog-and-became-an-astronaut
publishedDate: '2021-10-12 02:00 PM CST'
layout: ../../../layouts/BlogPostLayout.astro
---
Astro also has a Markdown component you can embed into your own components and layouts for rendering markdown content inline. Now when visiting a link to a blog post, it is displayed with the structure of the layout.
Additional migrations without special plugins
There were a few things that Astro didn’t provide me out of the box, including:
- Date formatting
- Reading time for blog posts
For date formatting, I installed the date-fns
npm package.
npm install date-fns
I used the format
function to give me a well-formatted date based on the publishedDate
from my markdown frontmatter. Read more about date-fns from their docs.
---
// BlogPostLayout.astro
import { format } from 'date-fns';
---
To add reading time for blog posts, I installed the reading-time
npm package.
npm install reading-time
After I mported it, I used it to read the contents of the markdown source compiled by Astro. There global Astro object provided the source of the markdown content for the blog post.
---
// BlogPostLayout.astro
import { format } from 'date-fns';
import readingTime from 'reading-time';
const { content } = Astro.props;
const stats = readingTime(content.astro.source);
---
The readingTime
function can read the source content of the blog post to calculate the reading time. This gave me stats from the blog post in minutes to add to the template of the BlogPostLayout.astro
file.
// BlogPostLayout.astro
<div class="flex flex-grow justify-center min-h-screen">
<article class="w-screen max-w-4xl p-8">
<h2 class="text-gray-600 text-2xl">{content.title}</h2>
<span class="font-light text-sm">
{format(new Date(content.publishedDate), 'MMMM dd, yyyy')}
-
{stats.minutes > 1 ? Math.ceil(stats.minutes) : 1} min read
</span>
<slot/>
</article>
</div>
Build and Deployment
To build the website, I used the npm command:
npm run build
Which runs Astro:
astro build
This produces the statically built version of the site.I can also preview the statically built version with:
npm run preview
I’m currently using Firebase to host my site, so there were no real changes here for deployment. Other static site hosts such as Netlify, GitHub Pages, Vercel, Azure Static Websites, and others are all supported.
After deploying, I must post the obligatory Lighthouse scores, so here you go.
Not bad for a day’s work, right?
Wrap-up
After the changes, this is what my current new stack looks like.
- Astro - Static site builder that includes Markdown, Prism, Routing, and more.
- date-fns - Modern functional library for working with dates.
- Firebase - Hosting solution for websites
- Prism Themes - Themes for syntax highlighting
- Reading Time - Calculates the minutes it takes to read the blog post
- Tailwind CSS - A utility-first CSS framework
One of the biggest things I gained was not having to rebuild my Angular application every time I made a change, and not having to rebuild all of my content when editing. If I’m working on a single blog post or page, only that page is built and served when I need it. Astro is still a work in progress, but I enjoy using it. I may need to add some interactivity to the site at some point, and I could do that with Astro’s support for integrating most frontend frameworks.
You can visit my site at https://brandonroberts.dev and my GitHub repo to see the full source code for my blog. Feel free to send me PRs for CSS 😉.
So what do you think? Leave a comment and let me know your thoughts.
Like this post? You can also follow me on Twitter, YouTube, Twitch, and GitHub!