feat: add Post 2024-03-02-version-3 (#2)

This commit is contained in:
Devin Haska 2024-03-01 21:16:09 -08:00 committed by GitHub
parent f5eaed457f
commit 7ec4550b5a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 469 additions and 1 deletions

View file

@ -56,7 +56,8 @@ pre:has(code) {
font-style: italic;
}
.token.attr-name {
.token.attr-name,
.token.property {
color: #73daca;
}

View file

@ -163,6 +163,7 @@ aside {
mark {
background-color: var(--color-border);
color: var(--color-text);
padding-block: var(--spacing-0\.25);
}
@media (prefers-reduced-motion: reduce) {

View file

@ -0,0 +1,466 @@
---
title: Version 3
excerpt: I'll show you the true power of being third!
tags:
- 11ty
- nextjs
- rebuild
youtube: true
---
Welcome to version 3.0! This has been my pet project for the better part of a year, with plenty of false starts and scrapped ideas.
First off, if what you're after is the source code - [here it is](https://github.com/wonderfulfrog/wonderfulfrog.com). If you'd like to learn more, read on dear reader!
Ive had a Figma file ready for months now that outlined my basic design - all I needed to do was build it. The problem was I could not settle on how I wanted to do it.
{% image "https://cdn.wonderfulfrog.com/figma-v3.png", "A screenshot from Figma showing my blog prototype. There are various elements visible like buttons and widgets. A colour scheme using teal.", "Figma screenshot" %}
{% image "https://cdn.wonderfulfrog.com/figma-v3-lightmode.png", "Another screenshot from Figma showing my blog prototype. This is showing the 'light theme' with a serif title font, monospaced body font, and teal accent colours.", "Astute observers will notice the current version doesn't look a whole lot like the site now!" %}
Something that always frustrated me was having my content along the code. I sought out a solution for this for months. I tried a whole bunch of headless CMS options like [Sanity](https://www.sanity.io/), [Hygraph](https://hygraph.com/), [Ghost](https://ghost.org/), and even hosting my own [WordPress](https://wordpress.com) (that was an exciting prospect until I discovered their mobile app does not support plugins).
I was always paranoid that bad actors would clone my git repo and steal my content, so I was so focused on finding a secure, obfuscated method to store content.
One day I thought to myself “who cares?” and it was like a switch flipped. Bad actors can still copy and paste everything, so having the content off the repo makes little difference. LLMs are exploding and would make it trivial to scrape my site (if they wanted to).
At last, that let me narrow down my options. Ill just keep the text content in the repo. But I still wanted a way to store images and other large media outside of the repo (more on that later. )
If the text content can be in the repo, then I can use anything! So lets stick with [Next.js](https://nextjs.org/)! … actually, lets not.
## An Aside on Breaking Changes
Anyone working in the web dev sphere knows things move fast. Too fast these days - in my opinion. Next.js happens to be one of those libraries. My site hasnt had any significant updates since 2022, which is approximately 2 years and change at the time of writing. In web years thats ancient.
I tried to migrate to the new [app router structure](https://nextjs.org/docs/app) and found myself getting stuck learning about breaking changes in Next.js and breaking changes in React[^1]. I spent way too much time trying to get things working with my tools that I didnt stop and think that my tools should be working for me.
I was originally thinking of [Astro](https://astro.build/), but its still using React. Im happy to use React for work but I dont want to have to deal with its newest idiosyncrasies in my own stuff.
Simply put modern React confuses the hell out of me. Im not all in for [“server vs client components”](https://nextjs.org/learn/react-foundations/server-and-client-components). Im certainly not the only one[^4]. I don't want this post to digress into my thoughts on modern React so I'm going to sidestep that for now. Im spending time wrapping my head around modern React - because I have to for my job - but for my personal site I'd rather things should be _easy._ I'm tired of having to deal with breaking changes all the time. I longed for the days of PHP where stuff just worked forever. It was time to embrace an old foe: boring technology.
Boring technology - to me, anyway - is something thats been around for a while, probably a few years. Its stable. Its been around long enough that someone has asked the basic questions on how to do XYZ on StackOverflow. And something important to me is that its also *averse to breaking changes*. It tries its best to avoid them, and if they must then there is a clear migration path and plenty of warning.
My first thought was WordPress. Thats nice boring technology. Battle tested. There are even some web dev folks out there using it as a headless CMS. I ended up not using it because the mobile publishing experience was no good. It didnt support plugins on the mobile app. I knew Id want something like [ACF](https://www.advancedcustomfields.com/) at least.
After a while I remembered a project Id seen ages ago that I knew a lot of folks were using. Its called [Eleventy](https://www.11ty.dev/) (or 11ty for short). [Theres a video of the creator trying out a blog using the latest version all the way back to the earliest versions of the project](https://www.youtube.com/watch?v=bPtQmsjXMuo)[^2]. Now thats what I like to see. Stable. Fast. Lets do it.
{% youtube "bPtQmsjXMuo", "How Stable is Eleventy? Can we run a 5-year old project as-is with Eleventy 2.0?" %}
So I decided to do what every web dev with a blog does: rebuild their blog from scratch. At least I had some content to play with!
## Cranking it up to 11(ty)
Eleventy is a static site generator. It does what probably other popular options like Next.js or Astro do. I chose it because:
- Its stable
- Its reliable
- Its fast
- It outputs pure HTML and CSS with no JS
- But you can add client-side JS if you want
- It uses Markdown files for content
- Has great documentation
- Minimal tooling required - its all plain JS
- The developer seems like a cool person
Those are my biggest reasons for choosing it. But it also does some cool stuff like:
- Grouping content into collections for easy parsing
- Converts JavaScript data stores into collections
- Has plugins for optimizing images
- Is fine with organizing things however you want
- Did I mention its fast?
A whole site build from scratch takes 30 seconds, and most of the time is fetching images (for optimization, more on that later).
I really liked how easy it was for me to organize content my way and tell 11ty how to consume it. It took a fair bit of learning on my part, but once it clicked I felt powerful.
Something Id like to mention and emphasize is the tool chain (or lack of). Under the hood, its all plain JS. Theres no [Webpack](https://webpack.js.org/), no [Babel](https://babeljs.io/). The only real dependency is [Node 18](https://nodejs.org/en). It uses CommonJS which is pretty old nowadays but again - boring technology! What it means is that I dont have to worry about things breaking because a dependency changed. Even as I write this with 11ty 3.0 on the horizon - it will still work with CommonJS with the option to opt-in to ESM. There are clear migration steps. Amazing!
## Let's talk CSS
11ty takes care of the content and leaves the developer to implement CSS however they like. It works great with vanilla CSS out-of-the-box, but mixing in PostCSS or Sass is easy enough.
I wanted to focus on vanilla CSS as much as possible, and use a little post-processing to tidy things up, add missing prefixes (if needed), and all the boring stuff. Otherwise, let's try using some new features like `:has`!
A methodology that resonated with me was [CUBE CSS](https://cube.fyi/). CUBE standing for <mark>Composition, Utility, Block, and Exception</mark>. The site has a great explanation of how that breaks down, but for my own purposes, I interpreted it as:
- Composition: utility classes that do one thing and one thing well, e.g. a wrapper class for centering a layout and giving it a `max-width`.
- Utility: Design tokens and extremely simple utility classes. Very similar to the utility classes that [Tailwind](https://tailwindcss.com/) provides, albeit fewer.
- Blocks: Like components in React. It's a wild west here, anything goes. What I found those was that my composition and utility classes did 90% of the work for me.
- Exception: Your odd one-offs (like arbitrary values in Tailwind). Uses `data` attributes as the selector because exceptions are represented by state changes (or React props, perhaps?).
I organized everything following this methodolgy, and added a `global.css` file that ties everything together with `@import`and `@import-glob` statements. I used PostCSS to process the CSS into a single file.
In order to include my CSS generation as part of 11ty's build process, I used a JavaScript [Template Data File](https://www.11ty.dev/docs/data-template-dir/) that processes the CSS using PostCSS, and the data file renames the file using the `permalink` property in the frontmatter. It leads to a single `style.css` that has everything I need - nice and clean, and no extra `npm` task required.
As part of my CSS build process, I inject my design tokens into the generated CSS from `global.css`. It's not the most elegant way I'm sure, but it works fine. Speaking of design tokens...
# Reinventing the wheel
I like Tailwind - especially for work - but I think it comes in heavy-handed. It does a lot, and I don't need the majority of it. I've seen some developers strip away everything but the design system that Tailwind provides out of the box, and I thought about doing that... but what if I did it myself instead?
Tailwind is yet another dependency. If I only need a small subset of its feature-set, why should I have to spend developer-hours stripping away everything I don't need and instead solve the problem myself with less code?
To be fair, I probably spent more time arriving at my final solution than I would've spent just using Tailwind, but it was a lot of fun!
All of my design tokens are stored in JSON files inside my `config/design-tokens` folder. They look like this (for example, this is `colors.json`):
```json
{
"light": {
"primary": "188deg 84% 35%",
"secondary": "8 84% 50%",
"background": "0 0% 98%",
"surface": "188 27% 94%",
"border": "188 48% 80%",
"text": "0 0% 4%",
"fadeText": "188 12% 32%",
"shadow": "188deg 100% 18%"
},
"dark": {
"primary": "188deg 84% 28%",
"secondary": "8 84% 43%",
"background": "0 0% 4%",
"surface": "202 10% 10%",
"border": "208 27% 15%",
"text": "0 0% 98%",
"fadeText": "188 12% 70%",
"shadow": "188deg 100% 18%"
}
}
```
I then wrote some JavaScript functions to transform these JSON values into CSS variables and utility classes. In order to achieve this I borrowed heavily from the Tailwind codebase. I decided which utility classes I wanted (in the case of colours, something like `bg-primary` or `text-primary`). Here is a (shortened) example of the output using thee `colors.json` tokens:
```css
:root {
--primary: 188deg 84% 35%;
--color-primary: hsl(--primary);
}
.bg-primary {
background-color: var(--color-primary);
}
.text-primary {
color: var(--color-primary);
}
```
An approach I borrowed from Tailwind was how to generate helper classes. I have an array of arrays that takes a desired helper class prefix (e.g. `bg`), and the corresponding CSS properties (e.g. `background-color`) and values. For example - colours again:
```js
const helperClasses = [
["text", ["color"]],
["bg", ["background-color"]],
];
```
I run this array through a `helperClassesToCss` function I wrote, which takes the helper class prefix, appends the colour name (e.g. `primary`) to the prefix, and sets the array of CSS properties to the desired value. If you're curious, here is the full source code with comments:
```js
/**
* Given an array of CSS properties, output css properties
* with each property equal to `value`
*/
const cssPropertiesToCss = (cssProperties, value) => {
return cssProperties.reduce((css, cssProp) => {
return css + `${cssProp}:${value};`;
}, ``);
};
/**
* Given a helperClass (string) and array of cssProperties,
* will generate a css class named helperClass that has
* all cssProperties mapped to value.
*/
const helperClassToCss = (helperClass, cssProperties, value) => {
const cssProps = cssPropertiesToCss(cssProperties, value);
return `.${helperClass}{${cssProps}}`;
};
/**
* Given an array of helperClasses that map to cssProperties,
* output a string of CSS that maps the helperClass (with variant modifier)
* to the array of css properties with each css property equal to
* value
*
* e.g.
* helperClasses = [["text", ["color"]]],
* variant = "primary",
* value = "#000"
*
* Will output the following:
* .text-primary {
* color: #000;
* }
*/
const helperClassesToCss = (helperClasses, variant, value) => {
return helperClasses.reduce((css, [helperClass, cssProperties]) => {
return (
css + helperClassToCss(`${helperClass}-${variant}`, cssProperties, value)
);
}, ``);
};
```
All of my code related to generating CSS from design tokens uses nothing but plain JavaScript and built-in modules (I think the only one being `path`). I'll never have to worry about my build process breaking. All of my design tokens are JSON. If I ever need to tweak them, the only thing I need to change is a couple of JSON files!
It took me a fair while to write, and certainly some trial and error, but I'm quite pleased with the final outcome. I can shunt this output into any CSS or future project if I want. If I want more helper classes, it's very easy to add more. There are quite a few spacing helpers:
```js
const helperClasses = [
["m", ["margin"]],
["my", ["margin-block-start", "margin-block-end"]],
["mx", ["margin-inline-start", "margin-inline-end"]],
["ml", ["margin-inline-start"]],
["mr", ["margin-inline-start"]],
["mt", ["margin-block-start"]],
["mb", ["margin-block-end"]],
["p", ["padding"]],
["py", ["padding-block-start", "padding-block-end"]],
["px", ["padding-inline-start", "padding-inline-end"]],
["pl", ["padding-inline-start"]],
["pr", ["padding-inline-start"]],
["pt", ["padding-block-start"]],
["pb", ["padding-block-end"]],
["w", ["width"]],
["h", ["height"]],
["size", ["width", "height"]],
["radius", ["border-radius"]],
["gap", ["gap"]],
["row-gap", ["row-gap"]],
["column-gap", ["column-gap"]],
["flow-space", ["--flow-space"]],
];
```
You'll notice one there that looks different - `flow-space`. I'm so happy that my approach works with setting CSS variables too. I use the `--flow-space` variable for controlling my `flow` composition.
## Quick composition chat
Compositions are cool. They're like little helpers that make your content looking good, without having to fuss too much with it. The Flow composition is arguably a fast favourite. Here it is:
```css
.flow > * + * {
margin-top: var(--flow-space, 1em);
}
```
Yep, that's it. It uses a fancy wildcard selector (`* + *`) to achieve the effect. It comes from [Every Layout's Stack layout](https://every-layout.dev/layouts/stack/)[^3]. Translated to English, it would be something like "for every child of `.flow` that is not the first, give it a `margin-top` of `--flow-space`, or `1em` if that doesn't have a value". The "not the first child" comes from the wildcard adjacent sibling selector.
What ends up happening is if you have a bunch of headers and paragraph tags on the page, this one single class styles it up perfectly so that it looks readable and beautiful. No extra work required, because the spacing value is `1em` it will use whatever the current child's `font-size` is. How freakin' cool is that?! I love it.
# Speaking of Cascade... The Data Cascade
[The Data Cascade](https://www.11ty.dev/docs/data-cascade/) is what I would consider to be 11ty's killer feature (like a [killer app](https://en.wikipedia.org/wiki/Killer_application)). In short, it allows for injecting data (or assembling data) practically anywhere, and rely on context in order to drive where that data goes.
The simplest data source would be front matter - the data that lives at the top of Markdown files. The neat part is that front matter can be added to non-Markdown files too, which allows for some potential fun stuff.
For example, my catalogue content is inside `/catalogue`. Inside that folder are more subfolders, and a [template data file](https://www.11ty.dev/docs/data-template-dir/) for the folder called `catalogue.json`. It looks like this:
```json
{
"tags": "catalogue"
}
```
Just one property but it achieves a lot. It applies the `catalogue` tag to every file inside this directory.
Because I'm using the `tags` feature, 11ty will automatically group everything inside this directory into a new collection called `catalogue`, which I can access from the global collections using `collections.catalogue`. With just a few lines of code I have _my entire catalogue_ in an array! No fetching or setup required. Done and done!
But wait... I can keep going... take for example my `/catalogue/books` folder. This one also has a template data file in here called `books.11tydata.js` which lets me use JS in here:
```js
module.exports = {
layout: "layouts/catalogue-item",
tags: "book",
permalink: "catalogue/books/{{ page.fileSlug }}/index.html",
linkTitle: "View book details",
eleventyComputed: {
tertiary: (data) =>
`<p class="[ flow-space-0.5 ]"><span class="[ text-fadeText ]">by</span> ${data.author}</p>`,
},
};
```
Lets go through this one:
- Every file inside uses the `layouts/catalogue-item` layout
- Applies the `book` tag (and therefore generates a new collection, automatically)
- Creates a permalink using the `fileSlug` variable per page
- Changes the `linkTitle` front matter
- Uses `eleventyComputed` (which is a special field) to inject data from the Markdown file
That's a lot! I'd like to turn your attention to the `permalink` variable. By using just this alone, 11ty will automatically generate HTML pages for each of my Markdown files inside this directory and pass in the front matter I set here. With just one line of code! What?! That's awesome!!
At this point all I have to do is make sure I have a layout file defined and built the way I like... and 11ty takes care of the rest. Love this!
But wait! There's more! What about [Global Data Files](https://www.11ty.dev/docs/data-global/)? Yup, we can do that too! For example, I used a global data file to fetch my latest Last.fm tracks:
```js
// Simplified imports
const EleventyFetch = require("@11ty/eleventy-fetch");
const dayjs = require("dayjs");
const fetchRecentTracks = async () => {
const url = `http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=wonderfulfrog&api_key=${lastFmApiKey}&format=json`;
const response = await EleventyFetch(url, { duration: "1m", type: "json" });
const tracks = response.recenttracks.track.slice(0, 5);
const recentTracks = tracks.map((track) => {
const timestamp = track.date
? dayjs(track.date["#text"]).utc(true).fromNow()
: dayjs().fromNow();
return {
artist: track.artist["#text"],
track: track.name,
url: track.url,
timestamp,
};
});
return recentTracks;
};
module.exports = async function () {
const recentTracks = await fetchRecentTracks();
return {
recentTracks,
};
};
```
This allows for my Last.fm data to be accessible via the global `data` field! The only catch is that this data is generated at build time and not live. This can be solved different ways, such as:
- Rebuild the site periodically
- Make a web component that fetches the data live
For now I'm content to trigger a build every so often in order for the data to be "live-ish". In the future it would be fun to build a web component (or perhaps someone out there has done that already)! 11ty is very compatible with web components.
## Alert the media
This was a thorny issue for me. I couldn't decide on how I wanted to manage media storage for a while. All I knew for certain was that I was tired of keeping it in my repo - it needed to be elsewhere.
As I dug into 11ty, I discovered a truly magical plugin called [`eleventy-img`](https://www.11ty.dev/docs/plugins/image/). It takes an image (either locally or remote), optimizes it, and stores it in the _output_ directory. The image can therefore be put anywhere you like, and with a small bit of shortcode it works like magic. For example:
```
{% raw %}{% image "https://path.to.image.jpg" %}{% endraw %}
```
It's a little bit of extra syntax compared to a Markdown image (and the newest version of `eleventy-img` doesn't even require shortcodes), but it saves so much manual effort of resizing and optimizing images.
That left the final question - where do I keep my media? I eventually settled on [Bunny.net](https://bunny.net/) - simple, no-nonsense storage with clear pricing. Has a REST API for uploading images, and even works with SFTP.
I decided to do the painstaking process of manually updating all of my content by myself rather than automating it. I figured the time investment of an automated solution would be roughly equal to the time it would take to do manually. Regardless, all my media is now behind a robust CDN (and with a custom domain too). When my site is built, 11ty will fetch all those images and generate local copies that are resized and output using `srcset`. It's all so seamless and easy, and `eleventy-image` even caches the results so subsequent re-builds are super fast!
## Other organizational details
The [Eleventy Excellent](https://eleventy-excellent.netlify.app/) starter was a huge inspiration for this site. I used its organization structure a lot. My `config` folder holds a lot of stuff:
- Custom collections, `config/collections`
- My design tokens, `config/design-tokens`
- Filters, `config/filters`
- Plugins, `config/plugins`
- Shortcodes, `config/shortcodes`
- Transforms, `config/transforms`
- Constants, `config/constants.js`
### Custom collections
These are my "shortcut collections" (or [Custom Collections](https://www.11ty.dev/docs/collections/#advanced-custom-filtering-and-sorting)) which help organize things for me. I have a collection for all my posts organized by tag (kind of like `collections.post.tag`), and a collection of my catalogues by type (e.g. `collections.game` or `collections.book`).
### Design tokens
As mentioned, a bunch of `.json` files that have my design tokens in here. That's it.
### Filters
[Filters](https://www.11ty.dev/docs/filters/) live here. Filters are great - lets me manipulate the data on my templates using Nunjucks. One of my favourite filters `organizeByDate` will take an array of posts (or anything with a date) and group them by year. It's the filter that makes the Posts and Catalogue pages show up by year!
### Plugins
11ty has a lot of [built-in plugins](https://www.11ty.dev/docs/plugins/), but also makes it easy to define your own. In my case I've defined a customized Markdown processor using [`markdown-it`](https://github.com/markdown-it/markdown-it). I added some neat features:
- [Footnotes](https://github.com/markdown-it/markdown-it-footnote)
- [Abbreviations](https://github.com/markdown-it/markdown-it-abbr)
- [Heading anchors](https://github.com/valeriangalliat/markdown-it-anchor)
- [Code highlighting using Prism](https://github.com/jGleitz/markdown-it-prism)
I'm also using the [`@11ty/eleventy-plugin-rss`](https://www.11ty.dev/docs/plugins/rss/) plugin to generate my RSS feed. I'm glad to see that 11ty supports RSS feeds out-of-the-box. I always hated that setting up an RSS feed in Next.js felt rather hacky.
### Shortcodes
[Shortcodes](https://www.11ty.dev/docs/shortcodes/) are cool. They're like custom Markdown but supercharged. I have my aforementioned `image` shortcode, and another for embedding YouTube videos using [`lite-youtube`](https://github.com/justinribeiro/lite-youtube).
### Transforms
[Transforms](https://www.11ty.dev/docs/config/#transforms) are great for modifying the template output. I only have one which minifies the HTML output using [`html-minifier-terser`](https://github.com/terser/html-minifier-terser).
### Constants
A couple of properties that are passed to my `.eleventy.config.js` file.
## Content authoring
To create and manage my content I use [Decap CMS](https://decapcms.org/) (formerly Netlify CMS). As far as I can tell it's the only game in town that does what I want:
- Git-backed file system that works with flat file structures
- Works on desktop and mobile
- Has preview deploys using pull requests
- Configurable schema for content
It's honestly not my favourite, but it works well enough that I want to continue using it. I've toyed with making my own solution for years, but I think that's something best left to someone who has time to not only build it but _maintain_ it.
The only thing I can't seem to figure out is a custom media library. As previously mentioned I use Bunny.net for all my media storage, and they don't offer a plugin for it. From what I can tell it's not possible to roll your own either.
## Hosting
My site is hosted on [Netlify](https://netlify.com/). I've used it in the past, and would've kept using it had I not switched to Vercel because I was using Next.js. I'm very happy with Netlify.
I use the [`netlify-plugin-cache`](https://github.com/jakejarvis/netlify-plugin-cache) plugin to keep my 11ty cache between builds. It really helps keep the build times down.
Whenever a change is detected on the `main` branch, a new version is deployed automatically.
## Conclusion
That was a lot, but I covered everything! I hope.
I'm pleased with this new iteration of my site. I won't kid myself and pretend I'll never built it from scratch again because... I am certain I will. I do feel like though things are in a great and maintainable spot that should hopefully be stable for years to come.
I've already got plans for my next steps... I would like to copy over my Letterboxd content into my catalogue so I can have everything live here. As well as my RateYourMusic ratings and reviews.
This has been a long (but fun) project, and I'm glad it's done. Now time to produce content!
## Additional notes
If you'd like to view the source for my website, it's [available here](https://github.com/wonderfulfrog/wonderfulfrog.com)!
Here are some resources I used (likely heavily) while building the site.
### Inspiration
- [Eleventy Excellent](https://eleventy-excellent.netlify.app/) ([view source](https://github.com/madrilene/eleventy-excellent))
- [Aleksandr Hovhannisyan](https://www.aleksandrhovhannisyan.com/) ([view source](https://github.com/AleksandrHovhannisyan/aleksandrhovhannisyan.com))
- [Andy Bell](https://andy-bell.co.uk/ ) ([view source](https://github.com/Andy-set-studio/personal-site-eleventy))
- [Cory Dransfeldt](https://coryd.dev/) ([view source](https://github.com/cdransf/coryd.dev))
- [Lea Verou](https://lea.verou.me/) ([view source](https://github.com/LeaVerou/lea.verou.me))
### Data Cascade
- [The Data Cascade](https://www.11ty.dev/docs/data-cascade/)
- [I Finally Understand Eleventy's Data Cascade.](https://benmyers.dev/blog/eleventy-data-cascade/)
### 11ty setup and configuration
- [Build a Blogroll with Eleventy](https://benmyers.dev/blog/eleventy-blogroll/)
- [From Wordpress To Eleventy With Ease](https://heydonworks.com/article/wordpress-to-eleventy/)
- [Optimizing Images with the 11ty Image Plugin](https://www.aleksandrhovhannisyan.com/blog/eleventy-image-plugin/)
### 11ty and design
- [What Are Design Tokens?](https://css-tricks.com/what-are-design-tokens/)
- [Configuring Web Fonts in 11ty with Global Data](https://www.aleksandrhovhannisyan.com/blog/configuring-web-fonts-in-11ty-with-global-data/)
- [Easily Use Design Tokens In Eleventy](https://heydonworks.com/article/design-tokens-in-eleventy/)
[^1]: Search for "nextjs react hydration error" to see what I mean.
[^2]: Here is [a post on 11ty.dev](https://www.11ty.dev/blog/stability/) with additional information, if you're curious.
[^3]: Every Layout is a fantastic resource and worth every penny. Check out the free options if you're not sure, and really try them out.
[^4]: People smarter than me have created posts outlining their problems with modern React. Here is a small sample: [Annoyed at React](https://blog.cassidoo.co/post/annoyed-at-react/) [React, where are you going?](https://dev.to/matfrana/react-where-are-you-going-5284) [Switching Costs](https://adactio.com/journal/20837) [Removing React is just weakness leaving your codebase](https://begin.com/blog/posts/2024-01-26-removing-react-is-just-weakness-leaving-your-codebase)