Mon Nov 14 2022
Frontend had never been my thing. I had always been more attracted to backend stuff: dealing with some serialization processes and a few data types had always been way more relaxing and interesting to me than dealing with the DOM.
When I started my first job as a web developer in an agency (surrounded by very clever minds) I didn’t know JS. And I felt already that everything was moving way too fast.
I should probably write about it someday, but I clearly developed a fear of Javascript all these years (CSS was not a great story either).
Recently when I decided to create this blog, I wanted to overcome my fear of JS and CSS. NextJS was a nice discovery (Thanks Olivier !). I had already worked with React a bit, but having a framework organizing everything was quite reassuring. When I also added TailwindCSS, I even started to have fun.
The speed of development was quite crazy. I still had a few things to care about, like creating the RSS feed, finding a way to fetch all my articles to build the homepage… But overall, it was a surprisingly pleasant experience.
I then moved the blog to Typescript which was again a surprisingly positive discovery.
The only thing that was bothering me was the size of the website. My blog was just static pages and the fact that the homepage was already a few hundred kilobytes made no sense.
Why send all this JS to the wire for a website that has no interactivity?
Thanks to the Fireship youtube channel, I discovered Astro. It felt like it was exactly what I needed. The idea of the possibility of not sending JS was just so appealing!
Moving from NextJS to Astro was really easy. I decided I would not use React anymore since my needs were quite basic and the astro template system was perfect for me. I enjoyed the separation between the rendered part and the data fetching part.
I needed to change the name of my files (from .tsx
to .astro
), to add the suffix in import statements (import ... from 'my-component.astro'
), to remove all these verbose export const MyElement = () {}
and change all the className
to class
(YES!).
Before:
import ALink from './a-link';
function getLicense(license: string | undefined): string {
if (!license) {
return '';
}
return '(license ' + license + ')';
}
interface Props {
author: string;
imageName: string;
license?: string;
source: string;
}
export default function ImageSource({
author,
imageName,
license,
source,
}: Props) {
return (
<div className="max-w-sm mx-auto text-sm lg:text-base">
{imageName ? (
<p className="text-center mx-auto">
<span className="italic">
<ALink href={source}>{imageName}</ALink>
</span>{' '}
by {author} {getLicense(license)}
</p>
) : (
<p className="mx-auto text-center">
Image by <ALink href={source}>{author}</ALink> {getLicense(license)}
</p>
)}
</div>
);
}
After:
---
import ALink from './a-link.astro';
function getLicense(license: string | undefined): string {
if (!license) {
return '';
}
return '(license ' + license + ')';
}
interface Props {
author: string;
imageName: string;
license?: string;
source: string;
}
const { author, imageName, license, source } = Astro.props as Props;
---
<div class="max-w-sm mx-auto text-sm lg:text-base">
{
imageName ? (
<p class="text-center mx-auto">
<span class="italic">
<ALink href={source}>{imageName}</ALink>
</span>{' '}
by {author} {getLicense(license)}
</p>
) : (
<p class="mx-auto text-center">
Image by <ALink href={source}>{author}</ALink> {getLicense(license)}
</p>
)
}
</div>
Very similar.
The children components are automatically available (not passed as parameters) through the <slot />
directive, replacing {children}
in React.
Before:
// h3.tsx
export default function H3({ children }: { children: any }) {
return (
<h3 className="text-lg border-l-2 border-pink-900 font-bold my-5 pl-4 font-sans">
{children}
</h3>
);
}
After:
// h3.astro
<h3 class="text-lg border-l-2 border-pink-900 font-bold my-5 pl-4 font-sans">
<slot />
</h3>
A greaaaaaaaat thing with Astro is the automatic syntax highlighting (happening on the server, not in the client). Damn, it made my life so much easier. It was quite a nightmare to make it work with NextJS (more on Astro and syntax highlighting in their docs ).
Another nice surprise is the Astro.glob
function that gives you a list of your files, including some exported variables and their path automatically.
const posts = await Astro.glob('./articles/*.mdx');
I also particularly enjoyed how easy it was to generate a full RSS feed. Damn. It was way more complicated with NextJS.
Here is the only thing needed, stored in /src/pages/rss.xml.js
(I used JS here instead of typescript because I didn’t want to bother with all the types ^^):
import rss from '@astrojs/rss';
const articleImportResult = import.meta.glob('./articles/*.mdx', {
eager: true,
});
const articles = Object.values(articleImportResult);
const recentArticles = articles.sort((a, b) => b.publishedAt - a.publishedAt);
export const get = () =>
rss({
title: 'Einenlum',
description: "Einenlum's blog",
site: import.meta.env.SITE,
items: recentArticles.map((article) => ({
title: article.title,
description: article.description,
link: article.url,
pubDate: article.publishedAt,
})),
customData: `<language>en-us</language>`,
});
And that’s all! More about RSS here .
One thing I regret a bit though is that you have to define your markdown equivalent components in every .mdx
file instead of doing it in one place. Example:
import H1 from '../components/h1.astro';
import H2 from '../components/h2.astro';
export const components = {
h1: H1,
h2: H2,
};
The exported components
variable holds the mapping between the markdown tags and the components to use. But it’s not such a big deal if you just import this mapping from somewhere else and assign it to the local components variable in every mdx file.
More about the MDX integration here .
You can see the previous version of my blog written with NextJS here and the current version written with Astro here .
Here is how what the main page loaded with NextJS:
Here is the global size:
Pardon my French… system (pun intended).
We can see we had 11 requests for a total of 307 kilobytes.
Here is the main page today with Astro:
And the global size:
We now have only 4 requests (no Javascript!) and only 50 kilobytes.
Some articles were quite heavy before, because I needed to create the syntax highlighting on the client side. For this article , there were a lot of files and bytes involved:
12 requests and 1.36 Megabyte just for an article with some codeblocks was really excessive…
Here is how it looks like now:
3 requests and 50 kilobytes!
This page’s size was divided by 21.
Now, I probably didn’t configure NextJS the best way, I admit. But you can’t beat a page without JS in terms of file size and browser being quickly responsive.
Using Astro was so pleasant.
I didn’t have to use Astro islands yet because all my website needs no JS. I’ll be curious to try this out if needed.
Till now, I’m happy.