· Learnings · 10 min read
Choosing a Framework for My Static Site
There are so many frameworks for starting a site. I went on a journey to find the right framework to build a home for Sea Otter Studios. Here are the reasons why I ended up choosing Astro.

I finally committed to the idea that I wanted to stand up a site for Sea Otter Studio, but with the plethora of frameworks available, it was difficult to decide on what I wanted to use. Coming from a React background, I was already familiar with just building a single-page static site or adding more complexity with the addition of Next.js. However, what piqued my interest was using a simplified static site generation for my static website. I figure that I get the dynamics of fast first page renders and dynamic components to build out the page, but the simplicity of full single page applications.
After contemplation, I had deduced my list of requirements had boiled down to:
- Static Site Generation (SSG)
SSG for simplicity and customizability - Blogging in Markdown
Content for this site must be written in markdown with or without a CMS - Good Dev Experience
Initially, I wanted to just use a theme and live comfortably in it, but there were always quirks and tweaks, therefore, I needed a dev-friendly framework
You may wonder: Why not just use no frameworks and build a static HTML site? I could do that, especially if I integrate daisyui or flowbite components from the CDN, However, I really enjoy the convenience of getting my packages from npm and the customizability of setting my component and importing it everywhere.
With the stage set, I went down a rabbit hole to learn about popular SSG frameworks, diving into the pros and cons before settling down on my end result of Astro.
All of these tools are fully featured and very adaptable, but they were way more complicated than what I needed for my static site. I decided to cross them off for a simpler solution. (kinda, see my post about reactivity in astro)
I started off my journey learning about Hugo. I am a big fan of Go since it is intuitive and runs very fast, which meant that I had a great dev experience! After getting through a quick read of the Hugo Quickstart guide, I figured I needed to install Hugo and then pick a theme as my starting point.
As a fan of Docker for setting up my environment and getting started quickly, I created the following compose file that worked with my WSL2.
volumes:
output:
services:
hugo:
image: ghcr.io / gohugoio / hugo:v0.135.0
ports:
- 1313: 1313
volumes:
- ./: /site
- output: /site/public
command: server --poll 300ms --bind 0.0.0.0
With that file, I needed to initiate the project with docker compose run hugo init
and a quick docker compose up
to get everything started. Next step: Installing a theme.
Hugo has a great theme library with a variety of options. I was quickly captivated by two: Anake for its i18n support and Blowfish for its use of Tailwind 3.0. To install modules with any go projects, you need to link the GitHub repository to your project. That is essentially what go mod
is doing for you. For Hugo themes, there are two methods: using hugo mod
or using git submodule
. There are requirements to getting hugo mod
to work, and as a newcomer to Hugo, it was a bit much to figure out so I opted for using git submodules. I toyed with both themes for a little bit, and then I decided that I liked the more polished look of Blowfish and ended up using that. Now for the fun part.
I got my Hugo server running, and I got my theme installed. This was the easy part! At this point, I had to figure out how to add content and, better yet, how to modify the pages. After a lot of reading and better understanding Hugo, I got the gist of it, which was:
- Add files to layouts/partials to override partials from the theme
- Add content to content/posts to create new content pages
- Additionally, you can add HTML to the markdown to update the content for the page, i.e. in content/_index.md
- Lastly, assets can be added to static/* and referenced by a relative URL where the root / is relative to the static folder
After all of this understanding, I started to want to change up the homepage so that I can look significantly from the Blowfish homepage. Adding elements was straightforward, but then I hit the challenging part: How do I change that dang background animation?! I mean the background animation looks really cool, but it is also distracting for building out my personal site.
I really had to dig deep and figure out that it was referenced in the Hugo config files found in the config/* directory. That directory already consisted of 6 separate config yamls, and one more was added to the root when following the quick start guide. Figuring out the differences between all of the configs took some time.
When trying to get answers to my questions, I discovered that the Hugo community was large, so quite a few questions were being posted about Hugo, meaning I was getting hits on my searches. But I found it was difficult to find the exact answers to my questions. It might just be that I am new to the framework, but I found it was unintuitive on understanding my routing, the differences in the configs and when to use configs vs partials.
After a couple of weeks, I figured I needed to abandon Hugo for something that I am more used to in the Typescript world.
At this time, I turned to Gatsby to continue my journey. Gatsby has extensive documentation and the community was also fairly sizeable. I headed straight to the Quick Start documentation, and with a couple of commands: npm init gatsby && npm run dev
I was off to the races. However, as always, WSL2 has some weird issues when working on the Windows mounted fs, so I had to tweak the commands to use CHOKIDAR_USEPOLLING=1
, and then hot reload started working!
New framework, new readings. I was checking out the framework, and it was considerably simpler than Hugo, so that fit the bill! I had my components directory, which was written in JSX, then I had my markdown posts files in src/pages/posts
. That was pretty straightforward, but now I wanted to change up the homepage. This was contained in the index.tsx
which was expected, except that even with CHOKIDAR
, it wasn’t hot reloading, what’s going on?! Turns out, the hot reload just rebuilds your post pages, so you need to trigger a full Gatsby rebuild to get changes on your components.
success compile gatsby files - 57.570s
success load gatsby config - 0.117s
success load plugins - 4.466s
success onPreInit - 0.009s
success initialize cache - 0.068s
success copy gatsby files - 1.202s
success Compiling Gatsby Functions - 7.337s
success onPreBootstrap - 7.533s
success createSchemaCustomization - 0.002s
success Clean up stale nodes - 0.006s - 5 / 0 0.00 / s
success Checking for changed pages - 0.001s
success source and transform nodes - 0.201s
success building schema - 0.162s
success createPages - 0.001s
success createPagesStatefully - 0.227s
info Total nodes: 41, SitePage nodes: 5(use--verbose for breakdown)
success Checking for changed pages - 0.001s
success write out redirect data - 0.002s
warn The icon(src / images / icon.png) you provided to 'gatsby-plugin-manifest' is not square.
The icons we generate will be square and for the best results we recommend you provide a square icon.
success Build manifest and related icons - 1.272s
success onPostBootstrap - 1.277s
info bootstrap finished - 115.165s
success onPreExtractQueries - 0.001s
success extract queries from components - 0.983s
success write out requires - 0.047s
⠀
You can now view gatsby - content in the browser.
⠀
http://localhost:8000/
⠀
View GraphiQL, an in -browser IDE, to explore your site's data and schema
⠀
http://localhost:8000/___graphql
⠀
Note that the development build is not optimized.
To create a production build, use gatsby build
⠀
success Building development bundle - 60.157s
Yeah, you read that right, on my 4GB RAM WSL2 instance, it was 2 minutes for a full build. Gatsby was no bueno for me.
I did some further digging, and I came across Astro. I heard about Astro around a couple of years back, and I was very interested in how Astro islands worked, which was very similar to React suspense components. This led me to my third and final adventure. As per usual, I went straight to their Getting Started page, and they were extremely minimal npm create astro@latest && npm run dev
. Boom, the astro project was initialized, and I was ready to cook.
Astro was simple and the builds were fast, less than 1s to start the dev server.
> astro dev
17:05:50 [types] Generated 3ms
17:05:50 [content] Syncing content
17:05:50 [content] Synced content
astro v5.5.4 ready in 221 ms
┃ Local http://localhost:4321/
┃ Network use --host to expose
17:05:50 watching for file changes...
17:06:22 [200] / 93ms
However, that is an unfair comparison when a theme hasn’t even been installed yet. Astro has an awesome library for themes, and I found Astrowind as the theme that I wanted. Astrowind looks and feels modern, as well as having live page examples and templates for a business site, a SaaS site, a blog and a personal site. Sea Otter Studio uses a mixture of components from each of those! After installing the theme, my startup got significantly slower, but bearable at less than 1s for an iterative build and 8s for startup:
> astro dev
16: 50: 24[astrowind] Astrowind `./src/config.yaml` has been loaded.
16: 50: 24[types] Generated 4ms
astro v5.4.1 ready in 8371 ms
┃ Local http://localhost:8080/
┃ Network use--host to expose
Now, to customize the homepage. The directory structure was very familiar to me:
- assets - Directory for all of your static assets
- components - Your component library. I would say your framework component library, but Astro actually layers on top of any front-end framework, or native web components
- content - Frontmatter configuration
- data - Your posts/markdown data
- layout - Your layout components
- pages - Your pages for routing
I started my journey at pages/index.astro
and I felt immediately at home. The entire theme was there to be edited, with no overrides and massive configs. The project was not absolved of configs, though, there was one in src/config.yaml
which hosted some base configs as well as your opengraph metadata. I was able to quickly build out components and reuse them like any other front-end framework, such as the one for an expandable image on hover. Astro definitely felt like it was built with the latest standards in mind and did not attempt to rewrite any mental models. I appreciated the clear direction without opinionating what kinds of components you can build since you can import any Framework Components into Astro. As much as Astro met my requirements, it wasn’t all rainbows and unicorns. After learning the Astro templating with the Frontmatter fences, I ran into some problems implementing reactivity on my site, and I wrote all about my exploration into reactivity on Astro.
Other mentions
I didn’t get a chance to learn and try out these frameworks, so I don’t have too much more to say about them. Hopefully, I can get around to trying them out someday!
Final Thoughts
This journey taught me a lot about how the dev community has been able to create unique experiences even though their function is more or less the same. With Hugo, I found that I learned about over-abstraction, where multiple configuration files may seem easier, but it was definitely less easy to understand. With Gatsby, I learned that a bad DX would inevitably turn developers away. Lastly, I learned that it is just fun and exciting in the dev world to learn about so many technologies that are endlessly changing! Although Astro isn’t a big kid on the block, I really believe in its potential. Astro was a wonderful experience and definitely has provided me with a better perspective.