A collection of roadsigns taken from 1996 through the present. The pictures pre-2001 were taken with a 35mm camera and scanned. The pictures post 2001 were taken with a digital cameras of improving picture quality. I am a software engineer by profession, and have a degree in Cartography, with an emphasis in Geographic Information Systems. As such, I have put together a system over the years that organizes the pictures by political jurisdiction, route numbers as well as a mapping interface. The photographs are mostly from the western half of the United States and Canada. Professionally, I normally spend my time working in the .NET, React/Typescript, and Azure technologies. This website, serve a dual purpose of publishing my photographs, but also allows me to experiment with new technologies. The next chunk of work I am looking at is to incorporate some AI tools to help me flag images with glare, low contrast, or other artifacts (rain drops, bug splattered windshield, etc…) that make the images less appealing to look at.
The application, is a static site powered by the Hugo static site generator. Over the years I have tried to server-side render the site (Django), as well as a SPA approach (React + Go APIs) but I have found that in terms of performance, it is faster to pre-render the entire site all at once. There still are a few dynamic elements on this website: search, and the random feature. Compared to most of the Hugo site examples, this site is really built around the various templating aspects. I’m not sure the developers of Hugo had this particular workflow in mind when they built the tool, but it works perfectly for my needs. The site uses the Tailwind library, and in particular the designs from Flowbite. CSS is generally my weakest development skill, so I have found that a CSS framework can make a professional looking website without me having to spend a lot of time messing with CSS. All of the actual signs photographs on this website are hosted in Cloudflare R2. I had the pictures hosted in AWS S3 and Cloudfront for years, but I found the hosting costs are significantly cheaper in R2. I can leverage the Cloudflare security and bot protections for the images. This setup seems to provide the lowest amount of maintenance, with the highest performance I have found so far.
The random highway signs utilize Cloudflare workers and the Cloudflare Workers KV product to generate random images. The workers are configured to redirect to a random sign, or return JSON to allow the client to handle the embed. I am using the Hono framework for my Cloudflare workers. The worker code does not update very frequently, so I do not include publishing the Cloudflare workers with the main deploy process. I have a separate GitHub action that publishes the workers when I specifically update the code.
The search is powered by a self-hosted instance of Meilisearch in combination with the InstantSearch tools developed by Algolia. The search widgets contain a majority of the JavaScript used on this website.
The mapping feature uses the mbtileserver tool to generate vector tiles at build time. The actual map is powered by Mapbox GL JS and provides a simple way to visualize the signs.
The build step is powered by GitHub Actions which is generally able to build and publish the site in under 6 minutes. On each build a whole new site is generated, the search index is updated, the signs (for random) are loaded into a Cloudflare Workers KV, and the vector tiles are rebuilt. I have tried this process with a few different static site generators, but Hugo is far and away the only one that can render the sheer number of pages for this website without crashing. The current page count is approximately 36,000.
content
directory. I am currently hosting the website using Fly.IO using gostatic. Previously I have tried different CDN or hosting technologies like (AWS S3/Cloudfront), or Netlify, but I found that the time it takes to sync and upload the new content is prohibitive - typically upwards of 20 minutes. With Fly.IO it is taking about 3 minutes to publish the entire static site.ogr2ogr
tool published as part of the GDAL suite to generate the vector tiles. The tiles are then loaded into mbtileserver. This whole process runs inside a Docker build step, so I do not need to install any of the GDAL dependencies into my build agent. The Docker container with the vector tiles are then hosted in Fly.IO. This whole process takes about 30 seconds.