2025 Update: I have since moved my blog to Github pages using Hugo with the PaperMod theme. The content of this post remains true, but note that I no longer rely on Google to host my content and use hacks to make RSS work. Someday I will write a follow up post on the move to Hugo and will link it here

I’ve always been something of a tinkerer. In high school and college, it was messing around with the game files of Call of Duty and Call of Duty: United Offensive to create multiplayer mods for myself and my friends. This might sound hard to believe, but in the late 00s and early 10s; Stack Overflow wasn’t quite the repository of knowledge it is today. When I had issues making the game run the exact way I wanted to, tinkering with my code or configs line-by-line is all I had. I consider it a blessing more than a curse, I learned a lot about coding and networking this way. Call of Duty:UO Box Art

Wondering what Call of Duty has to do with hosting a blog? Click Here to skip the fluff and just see the code of how I did it

Over a decade ago, I first purchased my domain name and was excited to get started utilizing it any way I could. With an old laptop from my college days and an unactivated copy of Windows Server 2012 in hand, I began a lengthy journey with self-hosting. I ran my own email server (using hmailserver), played around with Plex, and started learning HTML/CSS and running a WIMP (a Windows-IIS-MySQL-PHP) stack. I learned to make simple, interactive web sites. Eventually I got a Raspberry Pi and moved to raspbian based LAMP (Linux-Apache-MySQL-PHP) and a postfix based mail server. I started learning frameworks such as CSS Bootstrap and PHP Laravel. I started hosting more substantive services such as NextCloud. Eventually containers such as Docker became popular and I migrated my self-hosted apps to docker. Then I moved my docker containers from my Pi to a hosted VPS (Virtual Private Server). Then I moved my LAMP stack to Azure App Services just to see how it worked with my 12 month, $200 trial credit.

The point of this story? I like to do things the hard way. Just because I can. The stress of managing uptime with services, maintaining patches and security best practices, and dealing with email delivery problems wore me down. A few years ago, I gave up the self hosting life and home lab and planned a migration to the cloud. First to Office 365 and then to Google One a year later. Moving my document, photo, and media libraries were fairly trivial. Moving my hobby projects, not so much. I have a fairly expansive database of fantasy football data going back over a decade. I’ve used it as the data backend for applications, for analysis, for managing my team and for historical documentation for our league. For 12 months, I ran it in the Azure cloud until my free credits ran out. Moving it into the Google Apps ecosystem was not trivial, (and believe me, despite the fact that Google Sheets has this really nice QUERY formula, NO ONE should attempt to use Google Sheets as a database) but I got it done.

Moving my custom email domain to Gmail was very painless. (I had previously moved it from my self-hosted server to Office 365. While email can be self-hosted, I learned long ago that the money spent to make security, spam, and deliverability someone else’s problem is money well spent) So painless, in fact, that I went ahead and moved my whole domain registrar to Google Domains (RIP in peace) to make it even simpler.

The last thing to move was my personal website. I wanted to simplify my digital life with everything in the same ecosystem I had just moved to. Google Sites is a very simple What-You-See-Is-What-You-Get (WYSIWYG) website builder. It was not hard to get my homepage and resume back up and running. And yet, my website felt pretty empty with just 2 pages. The webmail, apps, and services, I had once so proudly hosted on my homepage had been replaced with Gmail, Drive, and whatever else. I decided that I would start a personal blog to give my website some life. I would fill it with the guides that I write for myself and others, reviews of my favorite retro games and just random thoughts in general. (This post falls into a bit of column A and column C, don’t you think?)

I could have just used an established blogging platform like Wordpress or even have put my blog on Blogger. Blogger is another Google Service, after all. As I said earlier though, I like to do things the hard way. I wanted my blog to look consistent with the rest of my website, I wanted it to be easy to export with the rest of my data, and I didn’t want ads on my content either. There are certainly better ways to accomplish this. Rising to the challenge of running a blog on a platform that wasn’t built for it pulled me in anyway. With Google Sites decided as my platform of choice, how exactly did I build my blog? Keep reading to find out.

Creating My Site

Homer’s Web Page

My site might not be as great as Homer’s but I think it’s pretty good. Click Here for that section of the guide to help you build yours. (Instructions for adding dancing Jesus not included)

The first thing I did was create a new folder to organize all the files related to my blog and created a new Google Site within it. Once I had the web design for my site figured out, I made a new page simply called blog. On this page I have a short description of my writing, a subscribe button, and an index of all posts. Using Google Sheets and the IMPORTFEED formula, I created an embeddable sheet to use as an index with links to my posts. By copy-pasting the link to the RSS Feed (more on that later) in the Feed Settings worksheet, the Feed worksheet automatically updates with the link to my new posts. I added the sheet to my blog page as an embed element with some custom HTML to make it look natural and not just like a spreadsheet. Despite the convenience of an automatically updating feed, I decided to manually update the index because Google Sheets is not responsive to screen size and so it was impossible for me to create an automatically updating index that looks acceptable on all devices.

I often compose my first drafts using Docs and will take screenshots and gather images off the web for my posts. To better organize all this, I created a sub-folder called blogs and then created a sub-folder with in that, for each blog post. When I am finished with my first draft of a post, I create a sub-page under the blog page I created earlier. On my site, I assign my blog posts a number for the custom path (located under “Properties -> Advanced”) and hide them from navigation. These settings are optional but I find that it makes it easy to share the links and keeps the navigation clean (otherwise every non-hidden page appears as a sub-link within the blog section)

Adding the Comments Section

Bruce Almighty clambering at the keyboard

It’s no good putting in all this effort writing a blog without a bit of “constructive feedback”, right? Click Here to see the guide for adding disqus or other methods such as mastodon replies for a comment section.

On my blog, I want readers to be able to interact directly with my posts. There are a few ways to do it. The old fashioned “post your email address at the end of the article and see if someone mails you” is one way. The way I decided to go about it was to use Disqus. Disqus is a third party comment platform that supports Facebook, X, or Google sign-ins or allows readers to sign up for a Disqus account and then engage with thousands of different sites including the sites of major publishers.

I created a publisher account on disqus and followed the instructions for the universal embed code. All I needed to do is create an embed element at the end of my posts and paste the embed code. I uncommented discus_config section of the code, replaced the PAGE_URL with the full URL of my post, and replaced the PAGE_IDENTIFIER with just the section of the URL after the domain. Just like that, disqus will make a new comment section each time I copy this code with updated variables. However, like any “free” service; if you aren’t paying, you are the product. Choosing Disqus definitely has privacy concerns. The trade-off of easily being able to have a comment section on a static site without an additional cost, was worth it for me.

Publishing Posts

RSS

With RSS, you can publish your content directly to your reader’s favorite feed reader app on their phone. Click Here to see the guide on how to set up your RSS feed

I had my content and a comment system, the next thing to do was to distribute it. Posting my content to socials only goes so far. I decided to set up an RSS feed for my blog. With RSS, people can follow along in their favorite feed reader without having to follow me on mastodon or constantly check my blog index page, hoping they catch a post when it’s relatively fresh.

RSS requires an XML file that is filled out according to the RSS or Atom spec. Since I chose Google Sites as my host, I needed a way to host my RSS file. Google Drive, naturally, made the most sense. I created a new Google Doc and filled out the feed settings according to the RSS 2.0 spec.

To add posts to a feed, add an item element within the channel element. Items need either a title or a description. The description could be a short hook to get the reader to click the full post, or it can just be the entire content. I’ve seen discourse around it both ways. I decided to post the whole HTML of the blog post in the description field. This approach is a something of a “best of both worlds” scenario as I can utilize Google’s and Disqus’ services for free but still can offer readers the ability to “opt-out” of their tracking by reading my content from their favorite feed reader.

To add content to a feed reader, users need to add the URL to the feed XML in their feed reader. Typically most blogs host the XML on-site so that they can reference it directly in the HTML of their website or directly as a button in the browser. Google Sites doesn’t allow us to do either unfortunately. With a bit of engineering, I was able to come up with a way that works. Google Drive doesn’t allow you to edit and share XML files directly. When clicking a link to an XML file, Google will scan it for viruses first. Good for user security. Unfortunately for my purposes, it breaks hotlinking to the XML for RSS use. I discovered that Google Drive will allow you link to a Google Docs file as a .txt file by using a specially formatted URL. Most RSS readers I tested will accept a link to a txt file so I had a way to get my feed to readers.

Automating the Feed

Manually editing the Doc containing the XML is very tedious, not to mention that post images are hosted by Google in such a way that copying the raw HTML leads to broken img links. In the name of convenience and of doing things the hard way, as is my tradition, I used the time I would have spent editing the RSS XML manually and instead came up with a way to automate it with Google Forms and Google Apps Script.

To get started, I imagined the workflow I wanted to easily publish posts.

  1. Create and publish the content on my Google Site.
  2. Enter the information needed to publish the post to RSS into a Google Form.
  3. Process the form submission and have Google Apps Script automatically insert items into the Google Doc with my feed XML.
  4. ???
  5. Profit.

Before designing my form, I went over the RSS spec and determined which fields I needed to gather in the form. The available elements for an RSS item are link, title, description, category, comments, source, guid, pubDate, author and enclosure. Using process-of-elimination, I determined which elements I didn’t care about.

  • Description will be pulled directly from the blog post HTML.
  • Comments are handled by Disqus. I wanted to give readers who wish to avoid Google and Disqus the option to just follow the feed in their favorite reader. For now I am ignoring the comment element.
  • Source is for reposted content that comes from another RSS feed, I don’t plan on reposting content so it didn’t apply in my case.
  • GUID is a unique identifier for the item in the feed. Using the isPermaLink=true attribute means this element is identical to link.
  • Author didn’t apply in my case either as I plan on authoring all the posts in my blog.
  • Finally enclosure is for a direct link to a media file. This is mainly used for podcasts, not anything that would apply to my blog’s feed.

The inputs I needed for my form were Title, Link, Category, and PubDate. With the fields determined, I started designing my form.

The Form

Mewtwo from Dragon Ball Z

Not the kind of form, we are talking about, but Click Here for an in-depth guide of how the form should look

I created a form with the following settings. For the Title field, I created a required “Short answer” input. No validation is needed. For the Link field, I created another required “Short answer” input. This time with text validation set to URL. I also created an optional “File upload” input. This is for gathering a thumbnail image for the post. Image hotlinks are broken if you try to copy them directly into an RSS feed from Google Sites, so instead I use this input to give the post a thumbnail in the feed. For the Category field, I created a “Checkboxes” input and gave myself some default categories to choose from. I also chose the “Add other” option so I could add a category if the ones I predefined didn’t suffice. These categories are arbitary as far as I know so if you were to do this yourself, feel free to set them to whatever is relevant to you.

Lastly I needed a way to set the PubDate. For fresh posts, that’s not a problem. I could just use the date and time of the form submission for the PubDate. But for updates or old posts that I want to publish, I needed a way to specify an earlier date. To handle this, I created a “Multiple choice” input titled “Republish?” with navigation set up to continue to next section on a “Yes” and to submit form on a “No”. Finally I created a second section of the form with a “Date” input with time and year included as options. In the settings, I set “Collect email addresses” to “Verified”. With this option set, I can validate that only the people I specify can update my RSS feed.

The Script

The Matrix Character Scroll

Trust me, this code is slightly less complicated than the matrix. Click Herefor the full script for automating updates to the RSS feed.

Now that I set up the form to gather the data I needed, I needed to write a script to process it and insert it into my feed. By opening the “three dot more” menu at the top right of the browser, I created a new Apps Script project for the form by clicking “<> Script Editor”. To get started, I needed to import the Cheerio library for manipulating the blog HTML.

The first section of my script is the global variables. I ended up with 6 of them. id, approvedSubmitters, approvedDomains, localTimeZone, parentDiv, and contentSection. id is the DocID of the Google Doc containing the feed xml. I found that by opening the document, grabbing the URL and copy-pasting the part between “https://docs.google.com/document/d/“ and “/edit”. approvedSubmitters is an array of email addresses of the users that I allow to contribute to my RSS feed. approvedDomains is an array of domains that are hosted on Google Sites and are approved to be posted to my RSS feed. localTimeZone is for properly appending the timeZone code to the pubDate. It is off by an hour for half the year because of Daylight Savings Time but I don’t care enough to implement a fix since not all locales have Daylight Savings Time and it doesn’t bother me that much if the pubDate in the feed is off 1 hour. parentDiv is a class selector for the parent div that holds images in the blogHTML. Because of the way Google Sites works, I can’t hotlink to images added to my site through the built-in image picker. I elect to just drop images from my post before adding the modified HTML to the feed. contentSection is a class sector for the content blocks in the blog page, this ensures that I’m only posting content and not the page header or footers.

My script contains six functions onFormSubmit, getBlogHTML, formatItemForRSS, updateRSS, buildRFC822Date, and addLeadingZero. onFormSubmit is the function that runs when the form I built has a submission. onFormSubmit opens the most recent submission and checks the submitter against the array of approved submitters I specified in my global variables. If the submitter is not one of the approved submitters, the script will throw an error and make no changes to the feed. If the submission passes email validation, it then sets the answers on the form submission to variables. The script does a second round of validation on the submitted URL to make sure it matches one of the approved domains from my global variables and that there were no typos.

If the URL passes the check, the script calls my next function getBlogHTML. getBlogHTML uses Cheerio to scrape the HTML of the blog and to drop any broken images from it. It has two parameters, the URL and a boolean parameter called “pop”. If “pop” is set to true, it will drop the last section of the blog HTML. Because I use the very last section of the post to embed the disqus comment section, I set this parameter to true. getBlogHTML returns the modified HTML of the blog post or false if the URL didn’t return a valid page. If getBlogHTML is false, the script will throw an error and no change to the feed will be made. If the blogHTML is valid and a thumbnail image was submitted, the script first shares the uploaded thumbnail to “anyone with link” and then prepends an img tag with the sharing link of the thumbnail as source to the beginning of the blogHTML.

The next thing my script calls is the buildRFC822Date and AddLeadingZeros functions. These functions are used from examples from Salma Alam-Naylor’s blog post. If the form submission has a specified post date, it uses the specified date. Otherwise it uses the time of form submission. Whichever date it is using, it runs it through these functions to get a valid pubDate for the RSS.

The next function called by my script is formatItemForRSS. This function takes the blogHTML, the pubDate, and all the other RSS item elements as parameters and returns a valid RSS item for the XML. Finally my script calls updateRSS which takes the formatted RSS item and the id of the Google Doc that contains the RSS feed and inserts it into the document 17 characters from the end. (17 characters is equal to the number of characters of the final closing tags of the XML) Once this final function runs and updates the RSS Doc, my submitted post is updated in the feed readers of my audience.

The Ugly

The ugly part of my setup? There is no easy way to share the RSS. Many sites have links to the RSS feed so that feed readers can automatically add the feed with just a link to the homepage. Most browsers send direct links to the feed XML to the default feed reader with a click of a button. Because I couldn’t access the underlying HTML of Google Sites and because Google Drive won’t let me hotlink to XML files directly; I have no method of doing this for my blog. Through a lot of brainstorming and finagling, I came to a workable, albeit “ugly” compromise. Many users of feed readers are pretty technically-abled, they can figure out how to manually add feeds to their readers if given a link to them. I made an embeddable button that automatically copies the direct link to the feed XML to the user’s clipboard so they can add it to their reader themselves. RSS Mobile, a feed reader for iOS, even has a setting to detect links to XML feeds straight from the clipboard. (It is just too bad that this setting is disabled by default). I put the button in the header of index page. “Subscribe to RSS” reads the orange button, a click updates the text to “Link to RSS copied” to let the user know it’s in their clipboard. From there, it is up to them to get that link into their preferred reader.

The So What

Current events have reinforced my belief more than ever that it is important for us as users of the internet and the greater “social web” to take ownership of our content and data, not allowing MegaTechCorps to take it hostage. As far as hosting and security is concerned, I don’t mind letting Google take the reins for now. I can still distribute my content without them by using RSS and I can use Google Takeout to make sure I have a local copy of my website and blog. From this process, I really learned to just take joy in being able to put my thoughts into words and to leave a memento about myself for the whole world (or even just my family and friends) to read. I hope the story of my journey getting a blog running on Google Sites inspires you to take the same control of your content back. For anyone starting a blog, I wouldn’t recommend using Google Sites but if you do, hopefully this blog got you started on the right path. And if you need help setting up a Google Sites blog so please reference this guide I made in Google Docs to help.