<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="/atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <id>https://duggan.ie/tag/content-warning-opinions/</id>
  <title type="text">@duggan — content-warning-opinions</title>
  <updated>2025-02-11T00:00:34.000Z</updated>
  <author>
    <name>Ross Duggan</name>
    <email>ross@duggan.ie</email>
    <uri>https://duggan.ie/</uri>
  </author>
  <icon>https://duggan.ie/favicon.ico</icon>
  <link href="https://duggan.ie/tag/content-warning-opinions/atom.xml" rel="first"/>
  <link href="https://duggan.ie/tag/content-warning-opinions/atom.xml?page=1" rel="last"/>
  <link href="https://duggan.ie/tag/content-warning-opinions/atom.xml" rel="self"/>
  <logo>https://duggan.ie/og-image.png</logo>
  <rights type="text">All rights reserved 2026, Ross Duggan</rights>
  <subtitle type="text">Posts tagged content-warning-opinions</subtitle>
  <entry>
    <id>https://duggan.ie/posts/ignoring-good-advice-and-building-my-own-blog-again</id>
    <title type="text">Ignoring good advice and building my own blog (again)</title>
    <updated>2025-02-11T00:00:34.000Z</updated>
    <author>
      <name>Ross Duggan</name>
      <email>ross@duggan.ie</email>
      <uri>https://duggan.ie/</uri>
    </author>
    <content type="html">&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;For various reasons, I stopped blogging about ten years ago.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;I ended up redirecting the energy into coding, and occasionally writing posts for the blogs of companies I worked for. But after Barricade was acquired by Sophos in 2016 I stopped writing almost entirely.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;My current company, &lt;/span&gt;&lt;a href="http://clearword.com" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;Clearword&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;, is winding down and I'm lucky enough that I don't have to line up any work immediately, so I've decided to start writing again –&amp;nbsp;and build some software along the way.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;It's kind of a classic developer mistake to build a blog before they start writing. &lt;/span&gt;&lt;a href="https://micro.webology.dev/2024/11/02/please-publish-and.html" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;As Jeff Triplett writes&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;/p&gt;&lt;blockquote dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;"Write and publish before you write your own static site generator or perfect blogging platform. We have lost billions of good writers to this side quest because they spend all their time working on the platform instead of writing."&lt;/span&gt;&lt;/blockquote&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;This is good advice. I am just preternaturally incapable of following it.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;figure class="image-wrapper"&gt;&lt;img src="/files/a1ab54a9fb768bfe.jpg" alt="lolscorpion.jpg" width="inherit" height="inherit"&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;The first time I build any significant software was when I cobbled together my own blogging system in college. It was styled with tables and rounded corner gifs and stitched together with PHP 4:&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;figure class="image-wrapper"&gt;&lt;img src="/files/e808965a05136485.png" alt="cult-redbrick.png" width="inherit" height="inherit"&gt;&lt;figcaption class="not-prose font-sans text-sm text-neutral-400 whitespace-pre-wrap break-words mt-2"&gt;Mad skillz.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;After college I used Wordpress, Blogspot and Medium as I moved away from writing web apps and into managing web infrastructure and writing backend services. For me, writing my own blogging system again ~20 years later feels like coming home.&lt;/span&gt;&lt;/p&gt;&lt;h3 id="anti-goals" class="group relative"&gt;&lt;a href="#anti-goals" class="absolute -left-6 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 text-gray-400 hover:text-gray-600 no-underline"&gt;#&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;Anti-goals&lt;/span&gt;&lt;/h3&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;When I was jotting down ideas, I put some of these under the heading "anti-goals" – stuff I wanted to consciously avoid.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;I didn't want to use a hosted third-party platform. I've gone down that route a couple of times now, and the results have always been unsatisfactory.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;I also wanted to explicitly avoid building anything that was throwing up newsletter popups, social logins, invasive analytics scripts, engagement tricks, progress bars and the inevitable creep of AI writing assistance.&lt;/span&gt;&lt;/p&gt;&lt;details class="bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg mb-2" open="true"&gt;&lt;summary class="cursor-pointer py-1 px-6 relative font-bold list-none outline-none text-zinc-900 dark:text-zinc-100 [&amp;::-webkit-details-marker]:hidden [&amp;::marker]:hidden before:content-[''] before:block before:absolute before:left-2 before:top-1/2 before:-translate-y-1/2 before:w-0 before:h-0 before:border-[6px] before:border-transparent before:border-l-black dark:before:border-l-white before:rotate-0 before:transition-transform [details[open]_&amp;]:before:rotate-90 [div[data-open]_&amp;]:before:rotate-90 hover:bg-zinc-100 dark:hover:bg-zinc-700/50"&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;On Ghost&lt;/span&gt;&lt;/p&gt;&lt;/summary&gt;&lt;div class="px-5 pb-1 pt-0 text-zinc-900 dark:text-zinc-100" data-lexical-collapsible-content="true"&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;One thing that I missed entirely in my first survey was &lt;/span&gt;&lt;a href="http://ghost.org" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;Ghost&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;. I remember looking into it a few years ago and not liking it for some (probably inane) reason. It's marketed as a professional publishing platform, with subscriber content, newsletters, etc., none of which is what I want. However, I ended up checking it out after I'd built a good fraction of my own blog and found they'd made similar choices to me, which means I think they are pragmatic and have excellent taste 🧑‍🍳.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/details&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;Something else I wanted to avoid was writing everything in Markdown and committing to git. There are understandable reasons why many developers choose it – they do not (as a rule) like running databases. Markdown is text, and git is basically a database for text without all the hassle of a live process and SQL.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;The idea of writing long-form English posts in Markdown is just anathema to me, even as a developer. It's barely a step above writing BBCode (shots fired!) and feels like giving up too much of the experience of writing. Fortunately this is the web, and we can all make different choices 🙂&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;What I do want out of my blog is still a work in progress. Partly what I want from it is for it to &lt;/span&gt;&lt;i&gt;&lt;em class="italic" style="white-space: pre-wrap;"&gt;be&lt;/em&gt;&lt;/i&gt;&lt;span style="white-space: pre-wrap;"&gt; a work in progress, not a publishing platform. I've spent the last fifteen years building software for other people, and I'm keen to get back to building software that is just for myself. Not that I won't share parts of it when I think I have something useful to share. However, I don't want to become a blogging platform maintainer. I don't want users. I don't want customers.&lt;/span&gt;&lt;/p&gt;&lt;h3 id="dual-stack" class="group relative"&gt;&lt;a href="#dual-stack" class="absolute -left-6 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 text-gray-400 hover:text-gray-600 no-underline"&gt;#&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;Dual stack&lt;/span&gt;&lt;/h3&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;Maybe it sounds obvious, but a blog is two software stacks – one for reading, and one for writing. It's much more obvious with static site generators, since you are perhaps using a text editor to write your posts, committing to git, and then the rest is taken up by a build process in Github, which creates the HTML, CSS, etc.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;I only really considered this when I started thinking about what I wanted from a blog, and two stand out goals were a rich Notion-like authoring experience, but a fast, cacheable, low-Javascript experience for readers. These goals seemed to be in conflict.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;Application developers build for multiple audiences all the time, but when you're building a B2B SaaS app that may just mean API consumers and logged in humans. Blogs are interesting because the largest visual component of the system should appear very similar for both sets of users, it's just that the author also needs a bunch of bells and whistles for managing the text.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt; For me, the ideal writing experience sits somewhere between the minimalism of Medium and the digital Swiss Army Knife documentation authoring of Notion (sans the slightly too in-your-face AI bits).&lt;/span&gt;&lt;/p&gt;&lt;h3 id="the-next-social-web" class="group relative"&gt;&lt;a href="#the-next-social-web" class="absolute -left-6 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 text-gray-400 hover:text-gray-600 no-underline"&gt;#&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;The next social web&lt;/span&gt;&lt;/h3&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;I used to be very online, and specifically very online on Twitter. That has waned in recent years, but I'm interested in where things are going with &lt;/span&gt;&lt;a href="http://mastodon.ie/@duggan" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;Mastodon&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt; and &lt;/span&gt;&lt;a href="https://bsky.app/profile/duggan.ie" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;Bluesky&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;. I have a preference for Mastodon, partly because Bluesky has more visible US political drama, and partly because I have an anarchist streak in me that wants to see social media unbundled.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;As a result, I want to make the blog have a native understanding of at least ActivityPub. I love the idea that my blog could be followed on Mastodon (still working on this). It will probably be a little complicated to provide a legible variant of the posts, but perhaps not much more complicated than Atom/RSS.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;Speaking of which, I also wanted to provide an Atom/RSS feed, even though I'm not convinced there's a large audience. I always wanted a nicely styled, standards-compliant feed though, so it's there.&lt;/span&gt;&lt;/p&gt;&lt;h3 id="passkeys" class="group relative"&gt;&lt;a href="#passkeys" class="absolute -left-6 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 text-gray-400 hover:text-gray-600 no-underline"&gt;#&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;Passkeys&lt;/span&gt;&lt;/h3&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;I follow &lt;/span&gt;&lt;a href="https://hachyderm.io/@rmondello" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;Ricky Mondello&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt; on Mastodon, and they have a nuanced perspective on password managers and &lt;/span&gt;&lt;a href="https://webauthn.guide" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;WebAuthn&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;. As a result, passkeys were unusually present in my thoughts when I was building the system, and so I included passkey support using the excellent (if search-resistant) &lt;/span&gt;&lt;a href="https://www.better-auth.com" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;Better-Auth&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt; library.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;Better-Auth has support for passkeys, but last I checked the user must first register with a password, which means passkeys are only a second-factor – not ideal. I haven't checked whether this can be easily patched yet, though it's on my list.&lt;/span&gt;&lt;/p&gt;&lt;h3 id="experiments-and-fun" class="group relative"&gt;&lt;a href="#experiments-and-fun" class="absolute -left-6 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 text-gray-400 hover:text-gray-600 no-underline"&gt;#&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;Experiments and fun&lt;/span&gt;&lt;/h3&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;I also wanted to have some fun, muck around and get deep into various parts of the stack. There's folk wisdom amongst software developers that &lt;/span&gt;&lt;i&gt;&lt;em class="italic" style="white-space: pre-wrap;"&gt;code is a liability, but knowledge is an asset&lt;/em&gt;&lt;/i&gt;&lt;span style="white-space: pre-wrap;"&gt;. The idea is that code requires maintenance, and the more of it you have the harder it is to manage effectively. It may even become an impediment to building what you need in the future. However, what you &lt;/span&gt;&lt;i&gt;&lt;em class="italic" style="white-space: pre-wrap;"&gt;learn&lt;/em&gt;&lt;/i&gt;&lt;span style="white-space: pre-wrap;"&gt; writing that code can have compounding value, making for better developers and better teams.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;That's not to suggest that I'm going to religiously account for every tangent as a "learning opportunity" – I'm my only real audience here. I don't have to attend a standup and sheepishly  tell people I spent a day or two making a kaleidoscopic rainbow button on a whim before discarding it as a bad idea. Or that while mucking around with SVG animations I accidentally created &lt;/span&gt;&lt;a href="http://duggan.ie/404" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;a noisy "O" effect on my 404 page&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt; and kept it because it reminds me of the Holy Grail from &lt;/span&gt;&lt;i&gt;&lt;em class="italic" style="white-space: pre-wrap;"&gt;Fate/stay night&lt;/em&gt;&lt;/i&gt;&lt;span style="white-space: pre-wrap;"&gt;.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;A lot of the original idea was just writing down developer experience goals like "I want to style everything with &lt;/span&gt;&lt;a href="https://tailwindcss.com" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;Tailwind&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;" or "I want to use &lt;/span&gt;&lt;a href="https://ui.shadcn.com" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;shadcn&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt; for editor components" and then doggedly pursuing those goals in the face of all headwinds just because it's an aesthetic choice I like.&lt;/span&gt;&lt;/p&gt;&lt;h3 id="technical-choices" class="group relative"&gt;&lt;a href="#technical-choices" class="absolute -left-6 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 text-gray-400 hover:text-gray-600 no-underline"&gt;#&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;Technical choices&lt;/span&gt;&lt;/h3&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;I'm in two minds about whether to even write these down at this stage, but maybe a quick overview is informative about where I started versus where I ended up? No blanket technological judgements intended 🙂&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;It's odd to realize this, but as someone with a long history in operations, operational simplicity was not an explicit goal when I began – I was initially more interested in how quickly I could put it together.&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;I think that's reflective of the last half a year or mad dash building &lt;/span&gt;&lt;a href="https://oggam.ai" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;Oggam&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;I started with &lt;/span&gt;&lt;a href="http://nextjs.org" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;Next.js&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt; and &lt;/span&gt;&lt;a href="http://supabase.com" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;Supabase&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt; because that's what I picked for Oggam, and I found them to be very powerful as someone who has not done much frontend dev for the last 15 years. Ultimately, though, after a couple of weeks I switched out to &lt;/span&gt;&lt;a href="http://astro.build" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;Astro&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt; and SQLite.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;I had started asking myself if the complexity of the Supabase self-hosted stack was something I actually needed for something as architecturally simple as a blog, and I felt Astro would give me more flexibility and control over the HTML that was being served. I'm also entertaining the idea that I can use Astro's static site building capabilities to regularly produce an entirely static failover/backup for the live version of the system. That would be pretty amazing.&lt;/span&gt;&lt;/p&gt;&lt;details class="bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg mb-2" open="true"&gt;&lt;summary class="cursor-pointer py-1 px-6 relative font-bold list-none outline-none text-zinc-900 dark:text-zinc-100 [&amp;::-webkit-details-marker]:hidden [&amp;::marker]:hidden before:content-[''] before:block before:absolute before:left-2 before:top-1/2 before:-translate-y-1/2 before:w-0 before:h-0 before:border-[6px] before:border-transparent before:border-l-black dark:before:border-l-white before:rotate-0 before:transition-transform [details[open]_&amp;]:before:rotate-90 [div[data-open]_&amp;]:before:rotate-90 hover:bg-zinc-100 dark:hover:bg-zinc-700/50"&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;On Supabase&lt;/span&gt;&lt;/p&gt;&lt;/summary&gt;&lt;div class="px-5 pb-1 pt-0 text-zinc-900 dark:text-zinc-100" data-lexical-collapsible-content="true"&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;I would pick Supabase again in a heartbeat if I were building another SaaS application. There are some things I would change in their stack around &lt;/span&gt;&lt;a href="https://supabase.com/edge-functions" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;Edge Functions&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt; (the hard CPU time limits are frustrating to work around), but I was very lucky to have picked this just based on a handful of Hacker News mentions and a YC podcast.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;If you embrace &lt;/span&gt;&lt;a href="https://supabase.com/docs/guides/database/postgres/row-level-security" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;RLS&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt; and Edge Functions you can produce some sophisticated behaviours while keeping infrastructure costs very low.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/details&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;I cycled through several text editors (&lt;/span&gt;&lt;a href="http://tiptap.dev" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;TipTap&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;, &lt;/span&gt;&lt;a href="http://platejs.org" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;PlateJS&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;, &lt;/span&gt;&lt;a href="http://blocknotejs.org" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;BlockNote&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;) for authoring, but found that many of them required payment for the functionality I wanted, or were too opinionated, focused on collaborative editing, or looked tricky to make modifications.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;I've been drawn to &lt;/span&gt;&lt;a href="https://lexical.dev" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;Lexical&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt; since I first found it, though since it's pitched as a framework for building editors I thought it would be overkill. I'd been steering clear of it in the name of keeping things easy, but in the end, a framework is actually what I needed.&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;Definitely a bigger investment of effort than the other editors, but has enabled me to be as idiosyncratic as I wanted in my choices, including rewriting chunks of it to use Tailwind and shadcn.&lt;/span&gt;&lt;/p&gt;&lt;h3 id="deploying" class="group relative"&gt;&lt;a href="#deploying" class="absolute -left-6 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 text-gray-400 hover:text-gray-600 no-underline"&gt;#&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;Deploying&lt;/span&gt;&lt;/h3&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;I'm not particularly misty-eyed over the days when hosting meant &lt;/span&gt;&lt;a href="https://web.archive.org/web/20110704151555/http://blog.boards.ie/2010/05/27/cleaning-up-a-few-years-of-incremental-infrastructure-growth/" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;buying a server and hauling it into a rack myself&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;, but there is something primordially &lt;/span&gt;&lt;i&gt;&lt;em class="italic" style="white-space: pre-wrap;"&gt;right&lt;/em&gt;&lt;/i&gt;&lt;span style="white-space: pre-wrap;"&gt; about being able to run everything on a single server, and copy files around.&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;At the moment the whole thing is deployed on a $6 Hetzner VPS via Github Actions, managed via Docker Compose and running behind nginx and Cloudflare. To me, this is a simple, powerful setup. With some tricks, it even allows me to get maybe-zero-noticeable-downtime deployments 😏&lt;/span&gt;&lt;/p&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;You can configure Docker compose to do rolling deploys (!):&lt;/span&gt;&lt;/p&gt;&lt;pre class="editor-code line-numbers" spellcheck="false" data-highlight-language="javascript" data-gutter="1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18"&gt;&lt;span style="white-space: pre-wrap;"&gt;    &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;healthcheck&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;      &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;test&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; &lt;/span&gt;&lt;span class="editor-tokenPunctuation" style="white-space: pre-wrap;"&gt;[&lt;/span&gt;&lt;span class="editor-tokenString" style="white-space: pre-wrap;"&gt;"CMD"&lt;/span&gt;&lt;span class="editor-tokenPunctuation" style="white-space: pre-wrap;"&gt;,&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; &lt;/span&gt;&lt;span class="editor-tokenString" style="white-space: pre-wrap;"&gt;"curl"&lt;/span&gt;&lt;span class="editor-tokenPunctuation" style="white-space: pre-wrap;"&gt;,&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; &lt;/span&gt;&lt;span class="editor-tokenString" style="white-space: pre-wrap;"&gt;"-f"&lt;/span&gt;&lt;span class="editor-tokenPunctuation" style="white-space: pre-wrap;"&gt;,&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; &lt;/span&gt;&lt;span class="editor-tokenString" style="white-space: pre-wrap;"&gt;"http://localhost:4321/health"&lt;/span&gt;&lt;span class="editor-tokenPunctuation" style="white-space: pre-wrap;"&gt;]&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;      &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;interval&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; 5s&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;      &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;timeout&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; 3s&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;      &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;retries&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; &lt;/span&gt;&lt;span class="editor-tokenNumber" style="white-space: pre-wrap;"&gt;5&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;      &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;start_period&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; 5s&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;    &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;deploy&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;      &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;replicas&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; &lt;/span&gt;&lt;span class="editor-tokenNumber" style="white-space: pre-wrap;"&gt;1&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;      &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;update_config&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;        &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;order&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; start&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;-&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;first&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;        &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;failure_action&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; rollback&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;        &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;delay&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; 0s&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;    &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;stop_signal&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; &lt;/span&gt;&lt;span class="editor-tokenConstant" style="white-space: pre-wrap;"&gt;SIGTERM&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;    &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;stop_grace_period&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; 60s&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;    &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;logging&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;      &lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;options&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;        max&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;-&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;size&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; &lt;/span&gt;&lt;span class="editor-tokenString" style="white-space: pre-wrap;"&gt;"10m"&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;        max&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;-&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;file&lt;/span&gt;&lt;span class="editor-tokenOperator" style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; &lt;/span&gt;&lt;span class="editor-tokenString" style="white-space: pre-wrap;"&gt;"3"&lt;/span&gt;&lt;/pre&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;However it did still result in 5-10 seconds or so of visible &lt;/span&gt;&lt;code spellcheck="false" style="white-space: pre-wrap;"&gt;&lt;span class="bg-gray-50 dark:bg-gray-800/30 font-mono px-2 py-0.5 rounded border border-gray-200/60 dark:border-gray-700/50 text-gray-800 dark:text-gray-200 relative before:absolute before:inset-0 before:bg-gradient-to-r before:from-blue-50/30 before:to-purple-50/30 dark:before:from-blue-900/20 dark:before:to-purple-900/20 before:-z-10"&gt;Origin Unreachable&lt;/span&gt;&lt;/code&gt;&lt;span style="white-space: pre-wrap;"&gt; errors from Cloudflare. Since this is a blog though, not a complex multi-user web app, I can cheat a little by getting nginx to serve "stale" cached content for the posts while the app backend is down:&lt;/span&gt;&lt;/p&gt;&lt;pre class="editor-code line-numbers" spellcheck="false" data-highlight-language="javascript" data-gutter="1
2
3
4
5
6
7
8"&gt;&lt;span style="white-space: pre-wrap;"&gt;        # Enable caching globally&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;        proxy_cache cache&lt;/span&gt;&lt;span class="editor-tokenPunctuation" style="white-space: pre-wrap;"&gt;;&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;        # Cache &lt;/span&gt;&lt;span class="editor-tokenFunction" style="white-space: pre-wrap;"&gt;validity&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; &lt;/span&gt;&lt;span class="editor-tokenPunctuation" style="white-space: pre-wrap;"&gt;(&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt;adjust &lt;/span&gt;&lt;span class="editor-tokenKeyword" style="white-space: pre-wrap;"&gt;as&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; needed&lt;/span&gt;&lt;span class="editor-tokenPunctuation" style="white-space: pre-wrap;"&gt;)&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;        proxy_cache_valid &lt;/span&gt;&lt;span class="editor-tokenNumber" style="white-space: pre-wrap;"&gt;200&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; &lt;/span&gt;&lt;span class="editor-tokenNumber" style="white-space: pre-wrap;"&gt;301&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; &lt;/span&gt;&lt;span class="editor-tokenNumber" style="white-space: pre-wrap;"&gt;302&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; 10m&lt;/span&gt;&lt;span class="editor-tokenPunctuation" style="white-space: pre-wrap;"&gt;;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;        # Serve stale content &lt;/span&gt;&lt;span class="editor-tokenKeyword" style="white-space: pre-wrap;"&gt;if&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; upstream is down&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;        # This does not serve stale &lt;/span&gt;&lt;span class="editor-tokenKeyword" style="white-space: pre-wrap;"&gt;if&lt;/span&gt;&lt;span style="white-space: pre-wrap;"&gt; upstream is healthy&lt;/span&gt;&lt;span class="editor-tokenPunctuation" style="white-space: pre-wrap;"&gt;.&lt;/span&gt;&lt;br&gt;&lt;span style="white-space: pre-wrap;"&gt;        proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504&lt;/span&gt;&lt;span class="editor-tokenPunctuation" style="white-space: pre-wrap;"&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;There are still plenty of bugs, but the bugs are all of my own creation, and most of them affect only me, so it's kind of great?&lt;/span&gt;&lt;/p&gt;&lt;hr&gt;&lt;p dir="ltr"&gt;&lt;i&gt;&lt;em class="italic" style="white-space: pre-wrap;"&gt;There is currently no comments system. If you'd like to share an opinion either with me or about this post, please feel free to do so with me either via email (&lt;/em&gt;&lt;/i&gt;&lt;a href="mailto:ross@duggan.ie" rel="noreferrer"&gt;&lt;i&gt;&lt;em class="italic" style="white-space: pre-wrap;"&gt;ross@duggan.ie&lt;/em&gt;&lt;/i&gt;&lt;/a&gt;&lt;i&gt;&lt;em class="italic" style="white-space: pre-wrap;"&gt;) on Mastodon (&lt;/em&gt;&lt;/i&gt;&lt;a href="http://mastodon.ie/@duggan" rel="noreferrer"&gt;&lt;i&gt;&lt;em class="italic" style="white-space: pre-wrap;"&gt;@duggan@mastodon.ie&lt;/em&gt;&lt;/i&gt;&lt;/a&gt;&lt;i&gt;&lt;em class="italic" style="white-space: pre-wrap;"&gt;) or even on &lt;/em&gt;&lt;/i&gt;&lt;a href="https://news.ycombinator.com" rel="noreferrer"&gt;&lt;i&gt;&lt;em class="italic" style="white-space: pre-wrap;"&gt;Hacker News&lt;/em&gt;&lt;/i&gt;&lt;/a&gt;&lt;i&gt;&lt;em class="italic" style="white-space: pre-wrap;"&gt;.&lt;/em&gt;&lt;/i&gt;&lt;/p&gt;</content>
    <link href="https://duggan.ie/posts/ignoring-good-advice-and-building-my-own-blog-again" rel="alternate"/>
    <published>2025-01-12T10:53:09.222Z</published>
    <summary type="html">&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;It's kind of a classic developer mistake to build a blog before they start writing. &lt;/span&gt;&lt;a href="https://micro.webology.dev/2024/11/02/please-publish-and.html" rel="noreferrer"&gt;&lt;span style="white-space: pre-wrap;"&gt;As Jeff Triplett writes&lt;/span&gt;&lt;/a&gt;&lt;span style="white-space: pre-wrap;"&gt;:&lt;/span&gt;&lt;/p&gt;&lt;blockquote dir="ltr"&gt;&lt;i&gt;&lt;em class="italic" style="white-space: pre-wrap;"&gt;"Write and publish before you write your own static site generator or perfect blogging platform. We have lost billions of good writers to this side quest because they spend all their time working on the platform instead of writing."&lt;/em&gt;&lt;/i&gt;&lt;/blockquote&gt;&lt;p dir="ltr"&gt;&lt;span style="white-space: pre-wrap;"&gt;This is good advice. I am just preternaturally incapable of following it.&lt;/span&gt;&lt;/p&gt;</summary>
  </entry>
</feed>