This site previously used Zola as its blog engine (you can read here for more details), along with:
- Alpine.js: A lightweight Vue.js alternative.
- Tailwind CSS: A utility-first CSS framework.
- PocketBase: An open-source realtime backend in one file.
- Deno: A modern runtime for JavaScript and TypeScript.
This architecture had been running for about 3 years. Mostly satisfied. Zola build fast, its functionality was basically adequate, with Deno, I could write API for frontend page. Using Alpine.js and Tailwind CSS, I could create frontend pages quickly.
However, it wasn’t perfect, mainly for these reasons:
- My go-to frontend library is React, but it’s hard to combine React and Zola in an elegant way.
- I want to handle HTTP server, Static Site Generation(SSG), and React in a unified way.
- This tech stack lacked some features I wanted, such as Server Side Rendering and using Shiki as a code highlighter.
After some investigation, I decided on Astro as my new site engine.
- It has all the features I wanted in a meta-framework.
- I’ve seen more and more tech influencers mentioning it favorably.
- It is listed in the ‘S’ tier in the State of JS 2023 survey.
- Cloudflare Developers is built using Astro.
However, making this transition wasn’t easy. On one hand, I was familiar with the current tools and workflow. Though it was missing some features, but it was tolerable. On the other hand, it would take time to become familiar with new tools, build a new workflow, and transfer the content.
After a period of deliberation, I decided to make the switch.
Design
The new site is separated into 3 parts: essays, comments and stream. Essays are articles I write on specific topics, stream is mainly for life activities, like books, photos and thoughts. Kind of facebook homepage, but just for myself.
Essays are written in MDX and generated by Astro. Stream contents are stored in PocketBase and rendered dynamically via Astro’s SSR. These contents are created using PocketBase’s built-in GUI. Comments are also stored in PocketBase, using PocketBase’s API.
Development
Astro’s Developer Experience is really good. It may take some time to get familiar with its directory structure, design philosophy and API. But once you get through this stage, you gain power. Thanks to the well-written docs and GitHub issues, whenever I encountered a problem, there was usually a solution.
- I wanted to provide a full-content RSS feed, but MDX files can only be fully rendered within
.astro
context. Thanks to the Container API (experimental), I nailed it. - I struggled for half a day trying to achieve swap HTML (from server) effects with an
.astro
component, but it was hard to rebind elements to JS functions. I ended up just using React.- You can’t bind js functions to jsx-like syntax, because they are not real DOM elements, and will be serialized, so js functions can’t survive.
<script is:inline>
can make HTML and scripts stay together, but after you replace the HTML with the new content, old scripts won’t work (cause they were bound to the old DOM).
- server islands are useful for non-server-related components. They work well for lists, or client-side interactions. When server contents are involved, DOM rebinding becomes an issue.
.astro
pages are like PHP, but more frontend-friendly..astro
components are handy for MDX.
It took me 5 days (working full-time) to design, develop, and transfer content. It wasn’t easy, but as the foundation for the next decade, it was worth the effort.
Deployment
This site is served on a VPS, so I chose Node as the adapter. In theory, it’s simple to deploy: just run pnpm build
and node ./dist/server/entry.mjs
, then make Nginx point the domain to the port. But reality taught me a lesson. When I ran pnpm build
, it took nearly 8 minutes and failed, due to running out of memory.
My first thought was that it allocated too much memory when optimizing images, so I set limitInputPixels: 10000
(100x100) in astro.config.mjs
to tell Sharp not to process large images. It didn’t work.
Then I found someone mentioning a command option --experimental-static-build
that might fix this scenario, but still, it didn’t work.
When I only left 1 post for Astro to build, it worked, though it still took about 5 minutes.
I was kinda desperate then, even considering rolling back to Zola, which at least wouldn’t cause these issues. This thought lingered for a while before I decided to overcome the problem.
since it could build on my machine, why not just skip the build process on the server and use my machine’s build result? So I put the dist
directory in git, and on the server side, I just ran node ./dist/server/entry.mjs
. Guess what? it worked! Though it was a bit quirky.
Just as I thought the mission was complete, something else happened. I wanted to write some cache files to the local disk. So I created a .env
file, set the cache path, then built and pushed to the server. Server crashed. After digging into the logs, it turned out the cache path was still my machine’s path instead of server’s path defined in the .env
file.
The .env
file is read at compile time, not runtime. I could remedy it by replacing all the env content before running, but it would be ugly and could lead to potential bugs.
Just when I was at my wit’s end, Bun saved me. Bun is known for its speed and efficiency, and Astro supports Bun, so I decided to give it a try. I ran bun install
and bunx --bun astro build
. Literally just these two commands, and it worked!
There were still some minor scenarios to handle (e.g. import.meta.env
is different when using Bun), but the deployment section basically came to an end. Bun is not only fast but also resource-efficient. In the future, I will prefer Bun over Node.
Conclusion
Astro as a meta-framework fully meets my requirements. PocketBase, combining data storage and CMS in a single file, is really convenient. I hope this new system can help me create more and better content in the future.