My Notes Were Gathering Dust, So I Built a Home for Them
Published on April 19, 2025 · 5 min read
For years, most of my notes stayed in Obsidian and Apple Notes. They were organized well enough for me, but they rarely became anything another person could read. I would add a few links to an idea and move on.
I wanted a public place where some of those notes could become finished articles. The source files had to remain portable, and publishing a post should require a content change rather than a code change.
The first version of this site used Astro. The site has changed since then, including a later move to a custom static generator, but the original requirements still shape it.
What I Needed
I compared three ways of publishing:
| Option | What it offered | What I would give up |
|---|---|---|
| GitHub | Markdown files with version history | Control over presentation and page metadata |
| Notion | A short path from notes to a public page | Control over hosting and the publishing flow |
| Static site | Owned source files and complete presentation | Time spent building and maintaining the site |
A static site gave me control over the source and presentation, including the URLs and page metadata. It also left me responsible for the build and deployment. That was a trade I was willing to make because I wanted to understand the whole publishing path.
The initial site needed a post index and an individual route for each article. It also needed an about page, RSS, image support, and a theme toggle. New Markdown or MDX files had to appear automatically and be sorted by publication date.
Why Static Generation Fit
The content changed when I wrote something, not when a reader opened the page. Generating HTML during deployment meant each request could be served as a file.
Client-side rendering would have moved basic article rendering into the browser. Server-side rendering would have introduced a runtime for pages that usually return the same content. Static generation matched the update pattern of the site and still allowed small client-side features where they were useful.
I chose Astro for the first implementation because it provided Markdown-based static output with optional client-side components. I deployed the built output through Cloudflare. That version taught me which features I actually used and which parts belonged in the build.
How Deployment Works Now
The current site no longer depends on Astro. In June 2026, I replaced it with a Node-based generator that reads the Markdown collections and writes the complete site to dist/. The build script produces both the pages and their supporting assets.
GitHub Actions runs that build and uploads dist/ to Cloudflare Pages. The workflow can start after a push, on a daily schedule, through a repository dispatch event, or manually. Those triggers are defined in the current deployment workflow.
The relevant deployment steps are:
- run: npm ci
- run: node scripts/fetch-data.js
- run: npm test
- run: npm run build
- uses: cloudflare/wrangler-action@v3
with:
command: pages deploy dist --project-name=blog-v2npm ci keeps the deployment tied to the lockfile. Tests run before the upload, and Wrangler sends the prebuilt directory to Pages. This is the direct-upload model documented by Cloudflare Pages.
The scheduled and dispatch triggers solve a separate problem: some published data changes without a commit. GitHub documents both schedule and repository_dispatch. A scheduled run may be delayed, so it is useful as a fallback rather than a real-time delivery mechanism.
Publishing Bookmarks Without a Runtime Database Call
After the blog was working, I added a public reading list. I wanted a phone-friendly way to save a categorized link without editing a Markdown file.
I used a Telegram bot as the capture interface and Cloudflare D1 as the store. The bot writes each bookmark to D1. During deployment, scripts/fetch-data.js calls the D1 query API and writes a local JSON snapshot.
The query names the public fields explicitly:
SELECT title, url, source, type, tags, date
FROM later_items
ORDER BY date DESCNaming the columns prevents a future database field from entering the public output by accident. The build divides the snapshot into /api/1.json, /api/2.json, and later pages. The reading-list interface fetches those static files as the reader scrolls.
D1 is outside the visitor request path. If the database or its API is unavailable during a deployment, the workflow fails before a new version is published. The existing site continues to serve its previous snapshot.
This design also makes the delay explicit. A new bookmark appears after a dispatch, scheduled run, or ordinary deployment. That is acceptable for a reading list; it would be unsuitable for data that must update immediately.
What Stayed the Same
The first implementation and the current generator use different tools, but they keep the same publishing boundary. Articles are source files in the repository. A build turns them into a versioned set of static assets, and deployment publishes that set together.
Maintenance did not disappear. I still handle dependency updates and build failures. Content format changes sometimes require migrations. Removing Astro reduced the runtime and build dependencies, but it also made the site generator my responsibility.
My notes now have a route from a private draft to a public page. I still decide which notes are worth finishing, but the mechanical part of publishing no longer gives me a reason to leave them untouched.