<< Blog Index

New Blog Smell, Again!
January 16, 2025

Last time I remade my blog site was about 3 years ago. Here I am again, having a little fun. My site in general was due for an overhaul.

I'm going for a more modern-but-professional look, nothing too fancy or flashy. If you want flashy you can always visit my hobbyist portfolio.

My previous blog site was a simple PHP application, separate from the landing page. This one is served by a Go backend. Actually, when I first wrote this, it was a single-page React app (see "site" which is the original iteration in the repository). I really like SPA apps—just the idea of a complete little app that runs entirely on the client side. Very cute. In this iteration everything was handled by the client, e.g., it would fetch blog articles in Markdown format and render them client-side.

However, I have some disturbing memories about Google and indexing SPA apps. Randomly, very randomly, even the simplest of apps would end up as a blank page. No error or reason why, it's like the Googlebot just decided to not behave that day. That would cause pages to disappear from Google. I'm not sure if that's still a problem today, but it was just a couple of years ago.

Rewriting in Go

Anyway, that's a good reason to have more fun, right? Rather than a typical React app—I mean, don't get me wrong, writing React apps is tons of fun, but it's nothing new. So, rather than a React app, I migrated everything to a Go backend. It's also neat to see it running on shared hosting.

You can't directly run a server application/backend on shared hosting since the app usually needs to be persistent. In this case, Go ships with a fastcgi library to set up a FastCGI interface easily. That's neat. I have a long-term Dreamhost plan for shared hosting, and it supports running .fcgi files directly. I tested and confirmed it's configured properly, spawning a process and keeping the process alive, so long as requests keep coming in.

Templ

When first fudging about with the Go backend, I was using the standard library's templating. It's very cool that Go includes a powerful template system as a standard library, but I could not for the life of me figure out how to have a subtemplate as a "pipeline", or an argument to another template. It turns out that you can't?! I was pretty peeved to waste multiple hours only to find that a basic functionality like that was not out-of-the-box. Take for example:

// Define new template
<MainWrapper>
   <div class="mywrapper">
      {children}
   </div>
</MainWrapper>

// Use template in another template.
<MyInstance>
   <MainWrapper>
      <h1>{title}</h1>
      <p>{content}</p>
   </MainWrapper>
</MyInstance>

There's no way to have a {children} insertion like that from another template unless you jump through hoops or have a simple string. So, I browsed about for alternatives.

Templ came up, something that provides similar behavior of React's JSX: inline HTML within your code, and it compiles down to normal Go code. I really like that approach, though I know it can be hairy to get the tooling right. I found the tools to be a bit rough around the edges, like go fmt wouldn't work well with it, or I'd get error popups from the VS Code extension, but I still really like the concept. I hope to see it improve over the years with more quality of life features, like with attributes instead of special syntax. The closer we are to normal Go and HTML syntax, the better, in my opinion. It just really, really eases the learning curve with templating.

It wasn't too hard to migrate the React templates to Templ, just a few naming scheme and function changes here and there, and voila – server-rendered pages. Here's a side-by-side comparison between a React component and a Templ template, for your curiosity:

templ blogSection() {
   {{ index := blog.GetBlogIndex() }}
   <section class="blog-section content-section relative" id="blog">
      @sectionHeader("blog")
      <a href="/blog/index" class="text-normal font-bold absolute right-0 top-2.5">View All &gt;&gt;</a>
         for i := 0; i < 5; i++ {
            if i >= len(index) {
               break
            }
            @blogExcerpt(index[i])
         }
      <p class="font-bold"><a href="/blog/index">Blog Index &gt;&gt;</a></p>
   </section>
}
export function Blog() {
   const [index, setIndex] = useState<BlogIndexEntry[]|undefined>([] as BlogIndexEntry[]);

   useEffect(() => {
      getBlogIndex().then((index) => {
         if (index) setIndex(index);
      });
   }, []);

   const excerpts: JSX.Element[] = [];
   for (let i = 0; i < 5; i++) {
      excerpts.push(<BlogExcerpt key={i} data={index ? index[i] : undefined}/>);
   }
   
   return <section className="blog-section content-section relative" id="blog">
      <SectionHeader name="blog"></SectionHeader>
      <Link to="/blog/index" className="text-normal font-bold absolute right-0 top-2.5">&gt;&gt; View All</Link>
      {excerpts}
      <p className="font-bold"><Link to="/blog/index" className="">&gt;&gt; Blog Index</Link></p>
   </section>;
}

Deploying to Dreamhost

Another minor complication is that Dreamhost can be unreliable with tools. I'll see it randomly terminate a compilation process sometimes because it's feeling fussy. That's no good. (Maybe I was doing something wrong, but it left me uneasy about anything other than serving web pages.)

Rather than compiling the Go app on the Dreamhost server natively, I decided to cross-compile (or "psuedo-cross-compile"? since I'm not running the compiler on Windows) locally and then copy the artifacts over. I do that with a small Dockerfile to run the compilation steps on Ubuntu, and then a docker-compose file to copy the artifacts into a mounted volume. Then I can copy those to the server directly.

An .htaccess file handles routing the backend requests to the FastCGI program. That's the only part that needs to be set up manually, since there is some other configuration in there. The rest of the artifacts are easy to deploy with automation.

Debugging

So how does this work with local debugging/testing? With React apps I have a basic test-server app that I use for debugging. It's included in my React/Tailwind/Typescript Bundle.

In this case, we have a little more to handle, such as the FastCGI server backend. I updated the test-server app to include a FastCGI reverse proxy. Any requests that are meant for the backend are routed accordingly. It uses the https://www.npmjs.com/package/fastcgi-client library, which, by the looks of it, is pretty new. Thanks to the author for sparing me from an ugly protocol.

I have a few npm scripts to run the test-server, tailwind, and webpack (watch mode), and my VS Code launch configuration includes templ generate as a prelaunch step to make sure the templates are up to date when restarting the backend locally. Ideally that should be a pre-build step, not a launch step, but I'm not too expert with VS Code shenanigans.

Concerns?

I wouldn't really recommend this path (Go backend) for shared hosting. In general, uncharted paths are usually not a great approach to something reliable. I'd consider this an experimental environment. I haven't seen any problems, but I feel slightly uneasy that a request will be terminated randomly due to it not being a normal/battle-tested operation.

I'd say PHP may be the best language for shared hosting, given its copy-and-paste deployment and mature ecosystem. It's so easy to work with. (But, PHP, right? Yuck!)

For personal projects I like to step outside of my comfort zone, learn a new thing or two while having fun. The final result stack is Tailwind, Typescript (minimal scripting, no React), Go, and Templ. Pretty neat! Check out the source on GitHub.

<< Blog Index