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
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 >></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 >></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">>> View All</Link>
{excerpts}
<p className="font-bold"><Link to="/blog/index" className="">>> 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.