On this page (click to expand):
But early in the project I hit on a problem that ironically seems to crop up for just this kind of perfect Jamstack scenario: how to enable the (individual, maybe not super tech-savvy) client to manage content in the easiest, most pleasant, and most cost-effective way? Given the simplicity of the overall site, it’s a much thornier question than you’d expect.
This post is a brief overview of how I used Airtable to solve this problem for this specific kind of small portfolio website, which led to some interesting results.
Markdown, while great for structuring content for blogs like this one, is in my opinion just not as versatile for managing content for such a portfolio project as a more traditional CMS might be. I wanted to easily scaffold a number of different content types (testimonials, projects, compositions, events, credits, etc.), and, while technically possible in Markdown, this would not have been ideal for client editing. It would be quite a hard sell to get a client with little web experience beyond Squarespace or WordPress to start using a text editor to make content changes locally and then push changes to a code repository. There are a few Git-based “Markdown CMS” options that exist which theoretically address this problem, but I don’t find them terribly impressive or intuitive (at least I’ll say that for Netlify CMS, an absolute wreck, although Tina CMS in conjunction with static site generation looks promising for future experimentation).
I had the pleasure of attending the Jamstack Conference in October (it was fantastic!), and something I overheard there rang true in my experience—that as the industry moves away from WordPress (which still powers a huge portion of the web, by the way), the use-case that WordPress was perfectly tailored to (namely, the personal/portfolio site of the type I’m talking about here) has been overlooked by most of the big-name, cutting-edge technologies.
For example, there is an amazing crop of headless CMS options out there at the moment, although most are geared toward enterprise users. I’ve had a lot of fun learning and playing around with Sanity and Storyblok in particular, but for the small personal portfolio site that WordPress excelled at, both feel a bit like bringing a Lamborghini to a go-kart track. The headless CMS space and its products are still evolving, so one day soon this might not seem like such a big deal. But WordPress was (is) unbelievably popular because, apart from any development considerations, it addressed this need for a simple, “plug-and-play” content solution with minimal customization and ease of editing for the non-technical user.
It was as a result of these problems and thoughts that I decided to try using Airtable as a content management system for this particular portfolio project—not an original idea by any means, but one that I had wanted to try my own way given the considerations above. Airtable, if you’re unfamiliar, is a kind of collaborative spreadsheet-database hybrid; it gives you all of the functionality of shared Excel sheets with the power of linked records and organizational methods you’d expect in a database. It’s a beautiful and intuitive product that I use regularly for my own work and personal needs. It also provides a straightforward, REST-like web API that allows the data you create to be used anywhere on the web.
So here’s how I set up an “Airtable back-end” for this personal portfolio site—a back-end that happens to be incredibly easy for a client to set up, use, and maintain (at no charge!). This approach offered a number of benefits and some minor drawbacks—for example, it would probably become a nightmare if you had more than a few dozen records in each table—but overall I think it’s a great solution for that small Jamstack site where you need a no-nonsense, easy-to-edit content solution.
Airtable: Scaffolding the Data
Getting set up in Airtable is quick and easy. First, I created a “base,” which is the central hub for a project that can contain multiple tables and users related to that project (helpful because you can work in real-time on structuring and adding content with your client). From there, I added separate tables corresponding to each page or content collection type. For example, the “Pages” table has some recurring fields for each of the website’s pages, like a title, subtitle, featured image, SEO description, etc., while the “Credits” table is structured to show a content collection with information like year, role, image, and IMDb link for movie and TV credits.
What I love about Airtable is that these tables are super fast to set up, and setting data types for each field is painless. Just like a headless CMS, there is a large variety of types to select from, from strings to multiple attachments to rich text to custom formulas. The table editor is also completely decoupled from the data fetched on the front-end, so it’s possible to sort and filter the data here without it effecting anything you’re doing with it elsewhere (again, useful for content editors who might want to customize the experience here without breaking anything on the live site).
Here’s an example of an asynchronous function that grabs an array of records from a given table, and accepts sorting and filtering parameters when called. For a static site where you might just need to have one set of the sorted and filtered data ready at hand, I found this to be ideal. (Following Astro’s project architecture, the example below is from a file of Airtable helper functions that lives in the
The function will return an array of objects from our specified Airtable table, which we can use in our page and component templates. For the “Credits” table, the JSON data will look something like this:
id: 'some id',
createdTime: 'some date',
Name: 'The Time Traveler's Wife',
Category: 'TV Series',
Role: 'Vocal Arranger',
Detail: '1 Episode',
ImageAlt: 'Poster for TV show The Time Traveler's Wife'
id: 'some id',
createdTime: 'some date',
Name: 'Space Jam: A New Legacy',
ImageAlt: 'Poster for film Space Jam: A New Legacy'
To render the data, I call the function on a given page, making use of Astro’s front matter to import and set variables specific to that page. In this example, I’m also using a “Credit” Astro component, which both accepts props and creates slots to server-render child content—in this case the
credits data we’re getting from the
getRecords() function call:
On the live site, the rendered and styled output looks like this (using a somewhat outdated slider powered by Flickity):
A note on images
Airtable recently announced that the URLs for attachments will now expire after “a couple of hours.” For this reason (and general performance considerations), it’s not a good idea to use the image links provided by Airtable directly in your markup or to treat Airtable as a “hosted service” for images/attachments. Instead, use something like the Astro or Eleventy image plugins when dealing with Airtable images, both of which will, among other things, convert an external source URL to a local image(s) at build time, according to your specifications.
Airtable Interface: Editing the Data
Now this is the part where it gets really exciting. While it’s possible to just leave things here and have a basic content management system that can integrate with any front-end, Airtable also offers an interface designer that allows you to repurpose your table data into custom visual interfaces. What this means is that you can effectively recreate a CMS “dashboard” within Airtable that makes the client editing experience even smoother.
Back in Airtable, I set up an interface that breaks out the various tables into “content collections,” which all show up on one dashboard. The records are shown as these nice little cards (with customizable preview fields), and editing a record is as easy as clicking into one and changing a field. It’s even possible to use different visual widgets for different data types (i.e., a calendar widget for event records—super cool!).
Publishing the site from within the interface itself is the one drawback to this approach. Unfortunately, on the free Airtable plan you can’t trigger scripts in an interface, so I’m not able to use a webhook to publish the site from my “dashboard.” It’s a minor (and understandable) annoyance, but it means that I have to have the client return to the table editor and click a button to run the build script, which isn’t as seamless as I’d like it to be. Still, pretty cool that you can deploy the site with a click of a button from within Airtable.
Here is how I set this site up to deploy to Netlify using a custom build hook. I added a new table in my base called “Publish,” where I have one record that uses the Airtable scripting extension to run the hook when the “Publish Site” button is clicked.
The Airtable scripting editor is a bit idiosyncratic in its JS syntax (the official examples only use
let for variables?), but it’s straightforward enough to implement a build hook like this: