tags:

tech

astro

blog

frontend

Writing a Blog using Astro

written by ixhby; published on 2024/01/27; markdown source

Hey everyone! I’m Emilia, also know as ixhby, transfem developer from germany mostly coding Rust and Web stuff. You may already know me as @ixhby@social.garnix.dev (previously @ixhby@transfem.social) (if you do, hii hehe :3)

In this Article I will try to tell you my experience with building this blog with Astro.

Warning: This Article contains strong opinions on Web Technologies, your favorite Framework will most likely be bashed.

Choosing a Framework

I fucking hate all frontend frameworks. React, Vue, {insert the 50 frameworks that spawned while I wrote this scentence} all have the fundamental flaw of plain overdoing it. There’s just simply no need for having your whole website dynamically rendered using some HTML/JS/CSS hybrid when you could just write the HTML/CSS/JS yourself instead. Granted, reactivity is a hard thing to implement youself and I also don’t want to do that, but having Reactivity using JSX as the default option instead of having to more explicitly enable it leads to a lot of unneeded complexity. There are a lot more critiques of Web Frameworks that I won’t got into here as to not derail too hard, like the problems with CSR.

One of the only frameworks I found that I’m 99.9% Satisfied with is: Astro!

Astro, unlike other frameworks, tries a more minimal, browser-agnostic approach to frontend. Without explicitly stating otherwise, astro compiles to 0 Bytes of Javascript. Pure HTML + CSS like the web was meant to be. A basic Astro “Hello World!” looks like this:

---
---

<html>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>

As you can see, this is just HTML. And that’s exactly what I like about astro. It provides you with an easy-to-use Development environment for HTML with (S)CSS, with optional tooling for dynamic content, both compile-time generated (SSG) as well as runtime generated using both Client-side rendering or Server-side rendering with integrations for major JSX Frameworks like (P)React. But, in contrast to other Frameworks, Astro always has the option of evaluating your dynamic content to static HTML at compile-time, like this:

---
// In SSG mode (default), this is evaluated at compile-time
// In SSR mode, this is evaluated at request-time
const num = Math.floor(Math.random() * 10) // Returns a random number from 0-9
---

<html>
    <body>
        <p>{num}</p>
    </body>
</html>

<!-- Evaluates to: -->
<html>
    <body>
        <p>0</p>
    </body>
</html>

In SSR mode, this site would be different every time you reload it. But in SSG mode, the javscript in the frontmatter and the templating is simply evaluated at compile-time and written into the resulting HTML. This evaluation of Javascript at compile-time brings a lot of powerful features with it, for example, dynamic compile-time inclusion of Blog Articles!

---
const allPosts = Astro.glob('../articles/*.mdx');
---

<html>
    <body>
        {allPosts.map((post) => <a href={post.url} >{post.frontmatter.title}</a>)}
    </body>
</html>

Astro, like all web frameworks, of course also has a Component System. It’s incredibly easy to use, just make a file in your components or layouts directory (only best practice, not acutally needed) and you have Your component. Want to pass components in your Component? Use the <slot /> tag. Want to pass some js variable? Define them in your Props.

---
// layouts/Root.astro
export interface Props {
    title: String
}
const { title } = Astro.props;
---

<html>
    <head>
        <title>{title}</title>
    </head>
    <body>
        <slot />
    </body>
</html>
---
// pages/index.astro
import Root from '../layouts/Root.astro';
---

<Root title="Index">
    <p>Hello Layouts!</p>
</Root>

There is of course, so much more to it, like the first-class Typescript Support or integrations, but these features alone already make it the perfect tool for building a blog from the ground up.

Building a Blog

I often find myself wanting to write long-form texts about the random Stuff I do (like making a blog, lol) but till yesterday I didn’t get to building a blog. I already knew how to do stuff with astro and had some time, so I built this blog.

The project setup with astro is incredibly easy, you either use the Houston tool to create a project with a TUI (pnpm create astro@latest) or manually make a pnpm project and add the dependencies and dirs. With that finished you find youself with an empty src/pages/index.astro page. Astro uses file-based routing, so everything in the pages/ directory translates to a route on your website.

Making a Base Layout

Layouts in Astro are incredibly useful at providing the scafolding and universal styling for your website, which is exactly what we’re going to do. We create a simple Layout called RootLayout that defines what we want every page in our site to contain.

---
---

<html>
    <head>
        <meta charset="UTF-8"/>
        <title>garnix.dev</title>
    </head>
    <body>
        <slot />
    </body>
</html>

But this naive approach has some flaws. We don’t want everyone of our sites to have the same title! How do we get a title for every page? We make title a prop!

---
export interface Props {
    title: String
}
const { title } = Astro.props
---
<html>
    <head>
        <meta charset="UTF-8"/>
        <title>{title}</title>
    </head>
    <body>
        <slot />
    </body>
</html>

If we now want to use the Layout, we have to set the title prop to our wanted page title like <RootLayout title="garnix.dev"></RootLayout>

With this done we can add some global styling. For this we can either use a <style is:global> tag or import a file from the styles/ directory. I’m gonna be using SCSS throughout this since it’s just better CSS and astro has first-class support for it.

---
import '../styles/global.scss';
// Ommited
---
html {
    background-color: #1e1e2e;
    color: #cdd6f4;
}

Now we have a nice catppuccin mocha background and text color on all our pages that use this Layout! We now need some kind of header for site navigation, for this we’re gonna make our own component which we will call Header

---
---

<header id="body">
  <nav>
    <h1 id="title"><a href="/">garnix.dev</a></h1>
  </nav>
</header>

<style lang="scss">
  #body {
    background-color: #181825;
    padding-bottom: 1rem;
    padding-top: 1rem;
  }
  #title {
    padding-left: 3rem;
    font-size: 1.5rem;
  }
  nav {
    display: flex;
    flex-flow: row;
    align-items: center;
  }
</style>

We also unset all changes to <a> tags so our title doesn’t look weird

// global.scss
a {
    color: unset;
    text-decoration: unset;
}

This component uses scoped styles, meaning all the SCSS in the style tag is only applied to this component. Now we just add some links to our other pages and add this Header to the RootLayout and we have our Header on all our pages!

<header id="body">
    <nav>
        <h1 id="title><a href="/">garnix.dev</a></h1>
        <p class="link"><a href="/articles">articles</a></p>
        <p class="link"><a href="/contact">contact</a><p>
    </nav>
</header>

<style lang="scss">
    // ommited
    .link {
        padding-left: 2rem;
        font-size: 1.25rem;
    }
</style>
---
import Header from '../components/Header.astro';

export interface Props {
    title: String
}
const { title } = Astro.props
---

<html>
    <head>
        <meta charset="UTF-8"/>
        <title>{title}</title>
    </head>
    <body>
        <Header />
        <slot />
    </body>
</html>

Writing Posts

Now we want to broadcast some of our well thought out and incredible posts on our page. But no-one want’s to write their blog posts in HTML right? That’s why we’re gonna use Markdown! (actually MDX) Astro has built-in support for rendering markdown like any other page. We can just make a pages/articles/ directory and write our articles there and they will be public. But now we have a problem. We wrote our thought-provoking revolutionary post about our opinions on tech, but when we check it out, it looks absolutely terrible! I thought we specified our color’s in the RootLayout already, why didn’t they apply here? Because we didn’t specify the Layout in our Frontmatter! Markdown documents need a layout: attribute in their frontmatter to be styled. For now, we can just set this to our RootLayout

---
layout: '../../layouts/RootLayout.astro'
---

# Why the npm ecosystem is actually perfect the way it is

oof, that’s better, no longer flashbanged by our own post. But this still doesn’t look good, our text isn’t centered at all, has no margins/padding, making our 100% correct tech opinions unreadable!

This is why we’re gonna make our own Layout for Articles. We simply extend our RootLayout with some divs and style to center text, which as we all know is incredibly easy to do. This is also where we see why we use SCSS

---
import RootLayout from "./RootLayout.astro";
---

<RootLayout title="Article">
  <div id="body">
    <div id="article-content">
      <slot/>
    </div>
  </div>
</RootLayout>

<style is:global lang="scss">
// Yes is:global here is terrible and there's probably a way better way to do this.
  #article-content {
    width: 100%;
    display: flex;
    flex-flow: column wrap;
    align-items: left;
    text-align: left;
    padding: 3rem 20% 0 20%;

    &>p {
      font-size: 1.1rem;
      &>a {
        color: #89dceb;
      }
      &>code {
        background-color: #313244;
        font-style: italic;
      }
    }
    &>* {
      padding-bottom: 1rem;
    }
    &>.astro-code {
      border: 0.2rem #181825 solid;
      border-radius: 5px;
      padding: 0.5rem;
      margin-bottom: 1.5rem;
    }
  }
  #body {
    display: flex;
    align-items: center;
  }
</style>

Wow, this looks way better! But there’s still some things we need to fix. All Articles have the same generic <title>, which is pretty bad. We want our readers to know that they should rewrite all the stuff they use in Rust even when they have not focused the tab. This is where markdown frontmatter comes in to save us once again. We simply add a title attribute to the frontmatter of all our posts and use it in the layout!

---
layout: '../../layouts/ArticleLayout.astro';
title: 'Why you should rewrite your nervous system in Rust'
---
---
import RootLayout from "./RootLayout.astro";

const { frontmatter } = Astro.props;
---

<RootLayout title={frontmatter.title}>
  <div id="body">
    <div id="article-content">
      <slot/>
    </div>
  </div>
</RootLayout>
<!-- Ommited -->

So we can just add arbitray Information to the frontmatter? We can use this for more than just the <title>, we could for example add some author information, published Date, tags and highlight the first heading by making it the title! And that’s exactly what we’re gonna do next.

---
layout: '../../layouts/ArticleLayout.astro';
title: 'Guys cooperating with Police on a private messenger isn't all that bad'
author: 'Element'
pubDate: 1984-01-01
tags: ['opression', 'tech']
---
---
export interface Props {
  title: String,
  author: String,
  date: String,
  tags: String[],
}
const { title, author, date, tags } = Astro.props;
---

<div id="article-header">
  <div class="tags">
    tags: 
    {tags.map((tag) => <p class="tag">{tag}</p>)}
  </div>
  <h1 id="title">{title}</h1>
  <p id="author-published">written by {author}; published on {date.slice(0,10).replaceAll('-', '/')}</p>
</div>

<style lang="scss">
  .tags {
    display: flex;
    justify-content: left;
    color: #a6adc8;

    &>.tag {
      margin-left: 1rem;
      padding: 0 4px 0 4px;
      background-color: #45475a;
      border-radius: 10%;
    }
  }
  #article-header {
    width: 100%;
    border-bottom: 4px #181825 solid;
    padding-bottom: 1rem;
    margin-bottom: 2rem;
  }
  #author-published {
    color: #a6adc8;
  }
</style>
---
import RootLayout from "./RootLayout.astro";
import ArticleHeader from "../components/ArticleHeader.astro";

const { frontmatter } = Astro.props;
---

<RootLayout title={frontmatter.title}>
  <div id="body">
    <div id="article-content">
      <ArticleHeader title={frontmatter.title} author={frontmatter.author} date={frontmatter.pubDate} tags={frontmatter.tags} />
      <slot/>
    </div>
  </div>
</RootLayout>

And now we have a fully styled article! If we now tweak some stuff in the astro config, we even have some cool code blocks!

// astro.config.mjs
import { defineConfig } from 'astro/config';
import mdx from "@astrojs/mdx";

// https://astro.build/config
export default defineConfig({
  integrations: [
    mdx({
      syntaxHighlight: 'shiki',
      shikiConfig: {
        theme: 'catppuccin-macchiato'
      }
    })
  ]
});

Listing all posts

Now we have some really cool posts but no one can see them :( We’re gonna have to make some kind of page that displays them all. For this we can combine the incredible Astro.glob() with our frontmatter metadata to create a cool preview for our articles. We’re also gonna add another frontmatter attribute to display a short description of the Article.

---
layout: '../../layouts/ArticleLayout.astro';
title: 'Our plan to steal as much money from Indie Devs as possible'
description: 'Our C-Suite came up with the brilliantly absurd idea of charging developers per game installed.'
author: 'Unity'
pubDate: 1984-01-01
tags: ['enshittification', 'big stacks', 'tech']
---
---
export interface Props {
  title: String;
  description: String;
  author: String;
  pubDate: String;
  tags: String[];
  href: String;
}
const { title, description, author, pubDate, tags, href } = Astro.props;
---

<div class="article-listing">
  <h2><a href={href}>{title}</a></h2>
  <p id="description"><a href={href}>{description}</a></p>
</div>

<style lang="scss">
  #description {
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 3;
            line-clamp: 3;
    -webkit-box-orient: vertical;
  }
  .article-listing {
    padding-bottom: 1rem;
    border-bottom: 4px #181825 solid;
    margin-bottom: 2rem;
  }
</style>

Now we have a article preview with title and description. But we still want the other metadata like tags, author and publish Date. For this we’ll simply extract the elements we used in our ArticleLayout into their own components and use them here.

---
export interface Props {
  author: String,
  date: String
}
const { author, date } = Astro.props;
---

<p id="author-published">written by {author}; published on {date.slice(0,10).replaceAll('-', '/')}</p>

<style lang="scss">
  #author-published {
    color: #a6adc8;
  }
</style>
---
export interface Props {
  tags: String[]
}
const { tags } = Astro.props;
---

<div class="tags">
  tags: 
  {tags.map((tag) => <p class="tag">{tag}</p>)}
</div>

<style lang="scss">
.tags {
  display: flex;
  justify-content: left;
  color: #a6adc8;

  &>.tag {
    margin-left: 1rem;
    padding: 0 4px 0 4px;
    background-color: #45475a;
    border-radius: 10%;
  }
}
</style>

Now we just make a quick component for that creates a list with the previews of all articles using our beloved Astro.glob()

---
import ArticleListing from './ArticleListing.astro';
// Sort all posts by Date descending (New -> Old)
const allPosts = (await Astro.glob('../pages/articles/*.mdx'))
    .sort((a, b) => new Date(b.frontmatter.pubDate) - new Date(a.frontmatter.pubDate)); 
---

<div id="article-list">
  {allPosts.map((post) => 
    <ArticleListing 
      title={post.frontmatter.title}
      description={post.frontmatter.description}
      author={post.frontmatter.author}
      pubDate={post.frontmatter.pubDate}
      tags={post.frontmatter.tags}
      href={post.url}/>
  )}
</div>

And we put it in it’s own page

---
import RootLayout from '../layouts/RootLayout.astro';
import ArticleList from '../components/ArticleList.astro';
---

<RootLayout title="Articles | garnix.dev">
  <div id="listing">
    <ArticleList/>
  </div>
</RootLayout>

<style is:global lang="scss">
  #listing {
    display: flex;
    align-items: center;
    
    &>#article-list {
        width: 100%;
        display: flex;
        flex-flow: column wrap;
        align-items: left;
        text-align: left;
        padding: 3rem 20% 0 20%;
    }
  }
</style>

Conclusion

Astro is cool af. Thank you for reading my blog, check out my other (currently nonexistent) posts or follow me on fedi @ixhby@social.garnix.dev to see my short-form posts and get notified when I post something new.