Building the Muscat Tech Calendar with Hugo

Myself and the OmanGo team have been busy building out Muscat Tech, which we introduced at a recent OmanGo meetup. The primary goal of Muscat Tech is to become the epicenter for all tech events in Muscat, enabling these events to become easily discoverable through a centralized calendar.

Muscat Tech Calendar

Muscat Tech Calendar

We’ve been running OmanGo for over 3 years now and in that time we’ve learned a lot about community-building. With Muscat Tech we’re hoping to inspire the birth of new communities to spring up focused on other tech topics, for example other programming languages, design, or even hardware.

We want to be the backbone for these emerging communities by providing the necessary resources and support. We would handle things like event promotion, venue booking, and sponsorship. Each group will have their own dedicated page on Muscat Tech with their details and a list of their upcoming events.

Muscat Tech Group Page

Muscat Tech Group Page

To encourage community contribution, we made the code for Muscat Tech open source. While we’ve released an initial version, we look forward to the community’s involvement in its continual development.

That’s an introduction in to the why of Muscat Tech. The rest of this post goes over the technical details of the website.

The Planning Stage

Front and center of Muscat Tech’s main page, we wanted a calendar showcasing all upcoming events. There were two ways we could achieve this:

  1. Add events to a public calendar like Google Calendar.

The upside here is the ease of adding events, and any changes to the events are automatically reflected on the calendar. The downside is that making changes would only be feasible for those who have the permission to edit the calendar. This is the approach that’s taken by technw.

  1. Create events from files in our Github repository.

This method allows the community to contribute by adding events through opening pull requests. The challenge here is finding a way to generate the underlying calendar events, and the need to rebuild the website and calendar each time a new git commit is pushed.

The Building Process

Initial Prototype in Nuxt.js

The initial prototype, brought forward by a team member, was built using Nuxt.js 3. They built a custom Vue component for the calendar that would fetch events from a server-side API.

The server side API was a function in server/api/meetups.ts that returned a list of events for a given month, year. In the prototype, the events were hardcoded as a single list of objects but the next step would have definitely been to read them from json or yaml files in the repository.

This would meet our requirement for being able to store events in the Github repository and allowing the community to contribute their events via pull requests. While this setup was adaptable and expandable, the server-side component seemed redundant since we could determine what the API would return at build time.

Muscat Tech in Nuxt.js

Muscat Tech in Nuxt.js

Static Generation with Hugo

Given my preference for simplicity, it felt like using a JavaScript framework and server-side component was overkill for the website’s needs. I pivoted towards a fully static approach but realized that it wouldn’t be completely feasible as we’d have to generate a new HTML page for each month/year. I settled for a mixed approach, generating everything statically, save for the calendar component which would use JavaScript to load event data.

Enter Hugo, a robust framework for building static websites. I’m already using Hugo for this blog and the OmanGo website so I was familiar with the tooling and the templating language.

Structure

With Hugo, we’re able to neatly arrange the events under their respective groups. Each group had its own folder, and every event was represented as a markdown file within that folder.

  • muscat-tech.org
    • content
      • events
        • omango
          • 2023_07.md event
          • 2023_08.md event
          • _index.md group details
        • omantel
          • _index.md group details
          • game-development-bootcamp.md event

Creating groups and events

New groups and events can be added by simply creating a new markdown file in the corresponding folder, complete with the relevant metadata (called ‘front-matter’) at the top of the file. For effortless creation of markdown files for new groups and events, we used Hugo archetypes.

E.g. here’s the markdown file for the OmanGo group (located at content/events/omango/_index.md):

---
name: OmanGo
description: |
  Meetup for people who know, use, or are just interested in starting to program in Go,
  the programming language designed and built at Google. All abilities are welcome.
email: hussein@omango.org
organisers:
  - name: Hussein Al Abry
    email: hussein@zidhuss.tech
    website: https://zidhuss.tech
    mastodon: https://omani.social/@zidhuss
    github: https://github.com/zidhuss
    linkedin: https://www.linkedin.com/in/hussein-al-abry/
where: Varies
when: Third Wednesday of every month
website: https://omango.org
type: group
---

Building the Calendar

Now we’re generating our webpages statically, we still needed to integrate calendar functionality. We used FullCalendar for this. It uses javascript to fetch event details from an API. Since we weren’t interested in running an API server, we needed a way to generate event data at build time for the calendar to fetch.

A viable solution was to have a pre-build script that generates JSON files for each month, like 2023-07.json. FullCalendar would then request these JSON files for the relevant months and display them in the calendar.

With this approach in mind I went to search on how to implement a pre-build step in Hugo. It turns out there is no native way to do this. I found this Github issue about implementing a post-build step. However the issue was closed with no implementation.

I ended up creating a custom script that I could invoke manually before running hugo. This would go through the structure of markdown files and generate the JSON files for each year month combination. The results are saved to the public directory — the output location for Hugo’s static files.

Here is a high-level overview of how the pre-build script works:

  1. It reads the file structure and each event file within the events directory.
  2. It extracts and validates the metadata from each file.
  3. If the metadata passes validation, the script proceeds. If not, an error is thrown, and the script halts.
  4. The script generates a JSON file for each year-month combination of events and saves it to the public directory.
  5. The script generates an .ics file for all the events, allowing users to subscribe to the Muscat Tech calendar with their favorite calendar app.

This approach worked well when building the site for production. However, it posed a challenge during development since Hugo’s server serves files from memory by default, excluding the newly generated JSON files. To solve this, we have to use the --renderToDisk flag which serves the files from disk instead of memory.

Continuous Deployment

Our choice of Hugo allows us to easily deploy our site on any static hosting provider. We chose Netlify due to its simplicity and generous free tier.

We’ve set up a Continuous Integration and Continuous Deployment (CI/CD) pipeline using Github Actions. This pipeline orchestrates our deployment process in the following way:

  1. Each time a new pull request is submitted, or a commit is merged on the main branch, Github Actions triggers our pre-build script.
  2. This pre-build script validates the metadata for each group and event, generating the necessary JSON files required by FullCalendar.
  3. If the metadata validation fails at any point, the entire process is halted and an error is thrown. This immediate feedback allows contributors to correct their submissions, ensuring that only validated and properly structured data makes it to the build process.
  4. Once the pre-build script successfully completes, Hugo takes over to build the site.
  5. The newly built site is then deployed to Netlify.

Netlify also offers preview deployments, allowing us to visualize changes directly from Github. This provides an additional layer of validation, enabling contributors to see their changes as they would appear live on the site.

This CI/CD setup streamlines our deployment process and ensures the high quality of our website. It gives contributors immediate feedback and allows for quick and efficient collaboration.

The site is live at https://muscat-tech.org

Wrapping up

We are excited about the potential of Muscat Tech. Our hope is for it to become a valuable tool for the Muscat tech community. We look forward to seeing how the community will shape its growth and development.

Contributions are welcomed. Whether you have a feature suggestion, found a bug, or want to help improve the code, feel free to dive into the Github repository and get involved.