Building a snack review website with Eleventy and Contentful
Full disclosure: at time of writing, I’m employed by Netlify, and I work there because I’m a fan of the platform.
One thing I’d never quite gotten around to in my Jamstack journey was using a headless CMS for a personal project. I suppose this is because I’m quite content to hang out in a Markdown file. I decided the time was more than nigh to try one out, and so I decided to create a snack review site, using Contentful!
World Snacks is a nice little excuse to try out international treats! All of this started when I was trying to track down a Ramune candy I’d had in Japan, and found japancandystore.com. I’ve since found a couple other neat sites, as well as some new-to-me local grocery stores to check out.
In any case, I grabbed my fave SSG (Eleventy), checked out this quick Eleventy + Contentful walkthrough, and got to hacking!
Content structure
Contentful enables you to create individual entries of particular content types within spaces (more on the data model), via a CMS dashboard. Data can then be fetched asynchronously, and rendered in the web project of your choosing.
On Contentful, I have a few different content types each with their own fields:
- Snacks. These have “reference” fields pointing to relevant Country and Snack Type entries.
- Countries
- Snack Types
- Pages (of which there is just one so far)
Integration with Eleventy
For most of the data above, integration with Eleventy goes a little something like this:
1. Create a global JavaScript data file
This data file initiates the Contentful JavaScript SDK client, supplying it with my Contentful space token and access token. Next, I call getEntries
for whatever content I’m querying at the time. Let’s say it’s Countries. My data file returns an array of Countries and their fields from Contentful.
2. Generate country pages via pagination
In a country.njk file, I use pagination with a size of 1 to generate a page for each Country.
3. Render the country page
A country.njk layout file then prints data for this Country, e.g. country.countryName
.
I also have a for
loop that renders the snacks in this country, using the Snacks data that was similarly pulled from Contentful. I ended up creating an Eleventy filter (filterByCountry
in my .eleventy.js
file) in order to filter the full array of Snacks down to only those in the relevant Country. This takes the Country name, then checks the Snack’s Country reference field for a match. It’s technically possible to filter getEntries
calls in a similar fashion, but I couldn’t figure out how to pass the Country name in as a variable to a Snacks data file…which I think makes sense given *handwaves* data lifecycles.
Anyway, that’s about it! Data is fetched live from Contentful any time the site is built, either locally using my build-and-serve command (npm run start
) or any time a new build is kicked off on Netlify. Which brings me to…
Building and deploy changes in Contentful to Netlify
When I was first putting together this site, I installed the Netlify app on Contentful, which enables you to manually trigger a build from the sidebar of the CMS. Funnily enough, I completely missed—until just now when writing this post—that there are simple auto-publish rules tucked a layer within the app.
Because I did want to automatically build and deploy newly published changes, I generated a Netlify build hook for my site, and registered it as a webhook on Contentful. Any time I (un)publish an entry or asset, a build kicks off on Netlify and grabs the latest data from the Contentful API (per the Eleventy integration I described). There are several other events to choose from as a trigger for webhooks.
Next I think I’ll filter triggers to particular content types. That way I can automatically deploy whenever I publish changes to a Snack, but not when I create a new Country; that could be empty until I “reference” it via a Snack.
I think I will keep using webhooks in lieu of auto-publishing with the app, because:
- There is far more granular control.
- The webhook logs let me view detailed information on when and why the webhook was triggered.
In general I would say Contentful’s user experience around webhooks is pretty nice. There’s a fair amount of detailed things you can do with them, but they’ve laid things out in such a way that make it pretty easy for me personally to digest:
Other Impressions on Contentful
In general, I found Contentful pretty easy to use, both from a CMS interface and headless API perspective. There are a couple minor things that are odd. For example, when viewing the “Snack” content type, the “Add” button is a dropdown, forcing me to take another click to add a new snack:
A split button might be more appropriate here.
The more meaningful gap I noticed is that Contentful doesn’t have a native concept of a “repeater” field. There is an app for that, but it’s limited to key-value pairs vs allowing you to repeat an arbitrary set of subfields (as Prismic’s group field does). Contentful’s play appears to be towards developer extensibility. Having built quite a few client websites in my day, though, I’d say a repeater field is worth building directly into the platform.
More fun, snacky things
To close out, I thought I’d share a few favorite details on this site.
For awhile now, I’ve been supporting both light and dark modes on websites, but always had that follow the user’s system. I finally got around to building in a theme switcher, in case you’d prefer to view this site in a different scheme than your system:
A coworker says that the dark theme reminds them of black currant candy packaging, which I am delighted by given that black currant is one of the world’s top tier candy flavors (IMHO).
I also got way too much joy out of my tortilla chip pagination icons:
And who doesn’t love a cheese puff pattern? I swear my time designing at Fuzzco has made me incapable of designing a subtle footer. There is just too much opportunity for one last burst of fun.
Check it out!
You can peek at the code on Github or check out the snacks. And I will always love to hear from anyone who has a snack suggestion. Happy snacking! 🥨