Features
- Responsive design that works on all devices
- Project showcase with filtering capabilities
- Fast performance with minimal JavaScript
- Built using modern web technologies
Tech Stack
Astro
Astro’s content-driven principle and focus on island architecture is the missing web framework I was looking for.
Before Astro, web frameworks delineated themselves into “sites with only HTML content” and “sites with only JavaScript”. You either went for a static site generator like Jekyll and forgo all your interactivity, or you went with an SPA framework like React and forgo using content-oriented file formats like Markdown. You either had an inherently SEO-friendly website, or you had an inherently interactive one.
What about Ruby on Rails, Flask, and the like? These frameworks require you to render your HTML entirely on the server; interactivity was provided by shipping some (probably untested) JavaScript. You would use something like jQuery to do DOM manipulation and Ajax. It’s very hard to fully take over on the client side and reap some of its benefits, such as lower latency, easier context management, etc. The web moved to SPAs for a reason.
The most popular framework that offered both easy content management and client interactivity is Gatsby. But Gatsby wedded you to React, and it was still using React to render content exclusively on the client side with JavaScript.
In recent years, server-side rendering has been making a return (thanks to Next.js). As a response, React added server components, which Gatsby uses to introduce partial hydration. While this is great for those who are already in the React ecosystem, I happen to not like React and its programming model very much. Still in the early days of my web development journey, I want to experiment with different approaches to web development, and Astro’s flexible integration with all kinds of front-end frameworks is a much-needed feature for me.
For a more detailed take on the JavaScript framework landscape, visit Allan Pike’s blog on the same topic.
TailwindCSS
TailwindCSS has become a mainstay in the web development circle for styling applications.
I strongly believe that the previous state-of-the-art of complicated CSS stylesheets with creatively named CSS classes to avoid collision is obsolete; it was an artifact of the web before React pioneered a “component-based” design approach. First, styled-components came along to really push the idea of coupling the CSS with HTML content inside a component. Then, Angular introduced scoping a .css
file to a component as its approach to component styling. More recently, bundlers like Vite formalized the pattern of “scoping CSS to a component” with CSS Modules. This pattern of CSS usage allows for easily debuggable CSS (because the “Cascading” part is gone), and eliminates the need for coming up with unique names for CSS classes.
People started packaging components with pre-defined styles to enforce a design system. There is React Bootstrap for Bootstrap, MaterialUI for Material Design, etc. Companies would distribute a component library packaged with well-defined CSS styles for each. The fundamental reusable block in web design is no longer content (HTML) and style (CSS), but components (HTML + CSS).
The problem with components is style customizability. Unless your components have a base headless component, you can’t just swap out the style of an element inside that component by passing in textSize="large"
. Without those props, you need to write long and verbose CSS that would (hopefully) override the styles defined in the component. But how does the new font size relate to the rest of the site? Most of the time, when you change the style in a component, you’re changing it in relation to another portion of your site (e.g., make this text larger than this other text). You’d need to know the internals of how the style of the other element is specified: Is the font size in px
, rem
, or em
?
Good design systems have well-defined CSS variables for things like “small font size”, “medium font size”, etc. to be overridden. Great design systems have well-defined classes for these common styles, like text-sm
, text-md
, because changing the font size also means changing the line height. TailwindCSS is that great design system.
DaisyUI
DaisyUI builds upon the primitives provided by TailwindCSS to provide more concise CSS classes for common parts of a website (buttons, navbars, etc.).
You can think of it like Bootstrap but designed on top of TailwindCSS.
I wanted a pure CSS library that provided some good-looking out-of-the-box classes. It had to be a pure CSS library because different parts of this website could be using different front-end libraries to render (a feature of Astro). A pure CSS library allows me to have a unified style in all parts of the website, regardless of the rendering technology.
I also wanted a library that was built upon TailwindCSS. Tailwind Typography allows styling of external vanilla HTML contents (like Markdown files) with Tailwind primitives. Having a CSS library that was built upon TailwindCSS allows content-driven parts of my website (like this page you’re reading) to look good without having to specify any custom stylings.
Deno
Deno is a server-side JavaScript runtime (yes, there are more than one) by the creator of Node.js.
In any project, I try to introduce a technology that is “white hot” in terms of risk. Deno is that technology for this project. I use Deno as the package manager and web server. All configurations and package management are done in Deno, making the most of Deno 2.0’s interop with Node.js.
There are some growing pains. The biggest one is that documentation for all other technologies in this website (Astro, Vite, etc.) doesn’t demonstrate how to get it working with Deno. Of course, thanks to Deno’s interop with Node.js, things actually work out of the box (which was a pleasant surprise).
One of the biggest confusions I had was how to deploy my site. I’m utilizing Deno’s workspace support, which has a section on containerizing a workspace member. The documentation shows that to containerize a workspace member, you only need to include the root deno.json
, the workspace member of interest, and all workspace members it depends on. However, this is not true, as deno install
needs every workspace member to be present. I’ve submitted the issue to the Deno team.
Another annoyance is the need to still include a tsconfig.json
file, even though Deno provides TypeScript out of the box. This is because Astro (and many other JS libraries) don’t generate types with valid triple slash directives, so a tsconfig
is still needed to make TypeScript language servers happy.