IndieWeb of these personal sites, and I’m all for it.
Also, there’s no better place to play around with new technology than your personal site, so welcome to the jungle.
These were my requirements for the build toolchain:
I initially adopted SvelteKit after seeing this excellent guide to building a blog with SvelteKit by Josh Collinsworth. I have always liked Svelte’s approach of doing as much rendering as possible during the build instead of having a large runtime library. SvelteKit did not disappoint. It easily met all my requirements.
However… I decided to give Astro a try just to see if it could possibly be any better than SvelteKit. And for my site, it actually did prove to be better for a few reasons:
I’m using the following Astro integrations and packages:
At first, my pages were not prefetching correctly even after installing the prefetch integration and adding rel="prefetch"
to my links. I didn’t realize my mistake until I noticed that the Age
response header was always zero for my page requests. While following a tutorial to set up my site, I had mistakenly enabled server-side rendering in place of static site generation. For static site generation, you do not want output: 'server'
in your Astro config, nor do you need to specify an adapter
.
Astro lets you specify a layout (an Astro component) in your markdown frontmatter, so right now, all of my markdown files start like this:
---
layout: ../layouts/ArticleLayout.astro
I wish there was a way to have all of my markdown files just default to this layout (Update: This is possible!). Regardless, by doing this, my article layout receives two props from the markdown: frontmatter
and headings
. I use these to display the title and publication date, as well as to build my table of contents. The actual markup generated from the markdown file is injected into a <slot />
in the layout.
Here is a heavily simplified version of my ArticleLayout.astro
:
---
import TableOfContents from '../components/TableOfContents.astro';
import BaseLayout from './BaseLayout.astro';
const { frontmatter, headings } = Astro.props;
const formattedDate = new Date(frontmatter.pubDate).toLocaleDateString('default', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
---
<BaseLayout title={frontmatter.title} canonicalUrl={frontmatter.canonicalUrl}>
<h1>{frontmatter.title}</h1>
<div>
<span>{formattedDate}</span>
{frontmatter.tags.map(tag => <span>{tag}</span>)}
</div>
<TableOfContents headings={headings} />
<article>
<slot />
</article>
</BaseLayout>
The content goes inside the <slot />
, and the full article with its title, table of contents, etc. are nested inside <BaseLayout>
. I plan on covering the table of contents in a future article, but let’s take a look at the BaseLayout.astro
as well. Again, this is a heavily abridged version with only the relevant bits:
---
const { title, canonicalUrl } = Astro.props;
import KldFooter from '../components/KldFooter.astro';
import Logo from '../components/Logo.astro';
import ThemeToggle from '../components/ThemeToggle.astro';
import '../styles/global.scss';
---
<html lang="en" data-theme="dark">
<head>
<!-- ... -->
{canonicalUrl && <link rel="canonical" href={canonicalUrl} />}
<title>{title} {title ? '|' : ''} Kevin Lee Drum</title>
</head>
<body>
<header>
<Logo />
<ThemeToggle />
</header>
<main>
<slot />
</main>
<KldFooter />
</body>
</html>
As you can see, the base layout provides the <html>
, <head>
, <body>
, etc. for every page on my site (not just articles). Everything we nested inside <BaseLayout>
in the article layout will take the place of the <slot />
above. I also passed title
and canonicalUrl
as props, and they are used for <title>
and <link ref="canonical">
elements respectively.
I will not fight anyone about it, but I like to use Tailwind for most of my styling when I can. It is a faster workflow for me, and it keeps me from having to create too many arbitrary one-off classes for the sake of “semantics”, especially when all I need is yet another flex box with centered children.
Since a lot of my content’s markup is generated from markdown during build, I am also writing quite a bit of sass within an Astro <style>
block. At first, a lot of it looked something like this.
article :global(ul) {
list-style-type: disc;
}
Like other frameworks, Astro scopes styling by default. The <article>
element is in the layout, but all of the paragraphs, subheadings, lists, etc. are one scope deeper inside the <slot />
, so we need to wrap those selectors with :global()
.
Alternatively, you can specify that your whole <style>
block is global, which is what I ended up doing.
<style lang="scss" is:global>
article {
ul {
list-style-type: disc;
/* ... */
There are a number of features I am still considering, such as webmentions, a Uses page, and a Now page.
I have already done a write-up on how I am using webfonts. A few other things I might cover in future articles: