the design and implementation of snailscale.io

like all good designs (not that snailscale is one) we start with the purpose and requirements. if you have read the front page of the blog

like many the story begins with the purchase of a domain name, snailscale.io. the name represents what this website is about, projects on a snail sized scale. so often i start time consuming projects only to stop right before finishing them and although i do get tremendous value from the half completed projects (through learning a bunch), it would be nice to finish some and show them off. so yeah thats what this space is about. you can expect to see technical blog posts diving into certain aspects of operating systems, how i build websites with frontend tech, the design process for pcbs, and even some 3d modeling stuff.

you might have realized that there isn't anything crazy here, its a pretty basic website. that being said there is a couple things that i want which some tech blogs don't have.

requirements

  • [x] the ability to write posts in markdown
  • [x] layouts (i don't want to have to copy the nav bar in every page)
  • [x] syntax highlighting in the codeblocks (this really helps me read code)
  • [x] the ability to group posts into projects for posts that are working on the same thing
  • [x] tailwind css (i have forgotten how to write normal css now)
  • [x] one click deployments (git push)

nice to haves

  • [] mermaid charts for diagrams
  • [] rss feed
  • [] sitemap
  • [] tags on posts to group by things other than projects (langs, area, tools)

tech stack

given the relatively simple requirements (and nice to haves) i wanted something stable, with plenty of resources, and minimal bugs. 11ty was the easiest option i saw for a completely static site. it also happened to meet all of my requirements basically out of the box.

implementation

using the 11ty starter as reference i created a similar structure

src/
    _data/
        metadata.json -- stores data about the website and rss feed
        projects.json -- a list of projects which align with the folders in posts

    _includes/
        layouts/
            base.njk
            prose.njk
            post.njk

    img/
        logo.png

    posts/
        series1/
            series.md

        posts.json -- applies some metadata to all files in the folder
        post1.md

    blog.njk -- home page for the blog
    projects.njk -- home page for the projects
    index.html -- home page

.eleventy.js -- eleventy plugins and stuff
package.json
.gitignore
README.md

ability to write in markdown ✅

done, 11ty supports this by default.

to get the list of posts i use a tag in the src/posts/post.json which applies to all file in that subdirectory. this becomes a collection which i can use programmatically.

layouts ✅

for the layouts we have a hierarchy

graph LR;

    POST-->PROSE-->BASE;
  • base.njk has basic stuff like the nav bar, logo, and the head.
  • prose.njk is for writing, it wraps everything such that the contents are easy to read (spacing, content width)
  • post.njk is for blog entries and adds the title and date from the post metadata

instead of specifying the layout in each file / post we use a posts.json in the posts folder which specifies the layout and tags to add for all the file in the subdirectory.

{
  "layout": "layouts/post.njk",
  "tags": ["posts"]
}

done!

syntax highlighting ✅

for syntax highlighting i choose to go with shiki rather than prism (default). i have previous experience with shiki and it allows me to annotate my code which is a feature i haven't used before but might now code annotation

const x = 10;

function test_function() {
  console.log("Hello world");
}

To add the plugin i did

npm i -D elenventy-plugin-shiki-twoslash

then added this code to my .eleventy.js file

const pluginSyntaxHighlight = require("eleventy-plugin-shiki-twoslash")
module.exports = function(eleventyConfig) {
    ...
    eleventyConfig.addPlugin(pluginSyntaxHighlight, { theme: "github-dark" });
    ...
};

done!

ability to group posts into projects ✅

the ability to have projects and have posts associated with those projects is what i want. for example i want each project to have a page which has high level information about the project, a link to the code, a link to the demo, and a list of blog posts that have to do with the project.

the way i have chosen to implement this is by having a file in the _data directory which has a list of all the projects. in the page-gen directory i have a file project.njk.html which generates a page for each project using the pagination api. for getting the list of posts for the project i filter the collection of posts by matching the project title to the post's project. this is done through the use of a customer filter added to .eleventy.js

eleventyConfig.addFilter("projectFilter", (posts, project_name) => {
  return posts.filter((p) => p.project === project_name);
});

to generate a page per project the template

---
layout: "layouts/prose.njk"
pagination:
  data: projects
  size: 1
  alias: project
permalink: "/project/{{ project.name | slug }}/"
---

<h1>{{ project.name }}</h1>

{% for name, link in project.links %}
  <a href="{{link}}">{{name}}</a>
{% endfor %}

<p>{{ project.description }}</p>

<h3>posts</h3>

<!-- set the posts -->
{% set posts = collections.posts | projectFilter(project.name) %}
<!-- render -->
{% include "post_list.njk" %}

to render the list of projects i just use the global data

<ul>
{% for project in projects %}
    <li>
        <span>
            <a href="/project/{{ project.name | slug }}">{{ project.name }}</a>
            - {{ project.description }}
        </span>
    </li>
{% endfor %}
</ul>

random note: i disable all templating in markdown and have to manually enable it per article that requires it

done!

tailwindcss ✅

tailwindcss is something i adore. for some reason it makes writing css almost easy and makes me worry far less about styling. we will get to adding it soon but something i have started to do with my websites is ensure they work without css first. its more of a bad heuristic than a proven solution but i find if i include a classless style sheet like pico.css or mvp.css (you can find a more complete list here) it makes it far easier to reason about what tags and structure my html should have.

for example it became obvious that the tag was wrong for the blog post pages when the formatting with mvp.css was wrong. it visually looked wrong and i knew to change it to <main> <article> (blog inside a article tag inside a main tag).

after fixing up the tags and getting the website to look half decent with mvp.css the website is in a very usable state.

  • the nav bar looks good
  • the content has nice x axis margins and spacing between paragraphs
  • the main page for posts and project displays the list of posts and projects without anything getting in the way

on the downsides some of the margins are wacky mainly due to the nav element having a huge bottom margin. overall however this strategy forces you to think about the semantic meaning behind your html rather than making everything a div and styling.

the SHA hash is 7295926ff91480d30534c999dea8ea00b79035ea for the mvp.css version of the website if you want to see what i mean.

now we move on to actually adding tailwind.

to start we need to install tailwindcss

npm install -D tailwindcss @tailwindcss/typography npm-run-all

then we update the build (npm) script to run tailwind alongside 11ty. npm-run-all handles running the build:* tag.

"scripts": {
    "build:ssg": "npx @11ty/eleventy",
    "build:css": "npx tailwindcss -i src/tailwind.css -c tailwind.config.js -o dist/styles.css --minify",
    "build": "run-s build:*",
    "serve:ssg": "npx @11ty/eleventy --serve",
    "serve:css": "npx tailwindcss -i src/tailwind.css -c tailwind.config.js -o ./dist/styles.css --watch",
    "start": "run-p serve:*",
    "debug": "DEBUG=* run-s serve"
  },

finally we add the link to the outputted styles in the base template.

<head>
  ...
  <link href="/styles.css" rel="stylesheet" />
  ...
</head>

I happen to use the typography plugin with tailwind to style generated html content, my config looks like this

module.exports = {
  content: ["./src/**/*.html", "./src/**/*.njk"],
  theme: {
    extend: {
      colors: {},
    },
  },
  plugins: [require("@tailwindcss/typography")],
};

done!

single click deployments (git push) ✅

i use netlify to deploy the site every time i run git push to the github repo. i cannot describe how easy it has made my life. there isn't much to describe here, you create an account with netlify, setup a site, point it at your source code, and tell it how to build your site and where the output is.

most of the time for standard static site generators it already knows where the output is and how to build your site npm run build

done!