Journal
A flexible way of using Nuxt.js with Contentful CMS
As mentioned in the previous post, I use Contentful as the CMS for my personal site, but I’m starting to migrate Cushion’s marketing website to Contentful as well. For my personal site, it was my first time integrating the CMS, so I mainly focused on powering the blog. That’s relatively easy because, at a minimum, you only need a title, date, and body. As soon as you get into complex pages that rely on custom content other than these fields, the solution is more up-in-the-air. In combination with Nuxt.js as my front-end framework, I feel good about the approach I narrowed in on, which makes it dead-simple to construct pages, while still providing flexibility and high fidelity.
Starting with routes, I decided to leave this to Contentful. With my personal site, I follow Nuxt.js’s directory-based routing approach by creating my route files in the “pages” directory and requesting each page’s content. For Cushion, however, I wanted to be able to create an entire page on-the-fly without touching code, so I created a “Page” content model.
So far, this model includes the title, path, meta description, and an array of entries. The “path” field is what lets me build pages directly from Contentful. I rely on Nuxt.js’s dynamic routes, which provide me with a parameter from the URL path to map to the page. Since the root’s path returns undefined
, I’m able to match the path or fall back to the homepage.
The other meta fields are self-explanatory, but the array of entries essentially represents each section of the page, starting with the hero. With this approach, the only job the page needs to do is loop through the sections and render them—and that’s exactly what the template does.
<template>
<main class="Page">
<component
:is="capitalize(section.sys.contentType.sys.id)"
v-for="section in model.fields.sections"
:key="section.fields.identifier"
:model="section"
/>
</main>
</template>
In the code, I rely on Vue’s <component>
element to provide a “wildcard” for components that I map from Contentful. As the template loops through the sections, it uses the :is
attribute with the Contentful model’s contentType
to specify the component, and then passes the content model to the component. (My components are capitalized, like “MyComponent” while Contentful returns IDs starting lowercase, like “mySection”, so I transform them to match)
The one caveat to know with this approach is that you need to register your component in Vue.js in order for the <component>
element to find and render it. By default, Nuxt.js auto-registers any component that it finds in your templates, but because this is a wildcard, Nuxt.js can’t know which components will be fed from Contentful’s payload. That’s totally fine, though. If you’re building a new component, you’re already working in the codebase, so it’s no problem to register it as well. I’ve also learned the hard way with Cushion that you want to manually register components instead of globally registering them, so you know the component dependencies of a specific component. Otherwise, if you ever need to clean up your code, you don’t know for sure whether a component is being used somewhere, and your bundle will always include it since you registered it globally.
For the components themselves, I’m modeling them as I go—keeping it simple, but also keeping an open mind. I’m not afraid to scrap something and recreate it if I find a better way to structure the content later. I will say that this approach gets me so excited. When I need a new section, I design it, model it, code it, and then it’s wired up—and now I can change its content no problem in Contentful. Sure, that’s the whole point of a CMS, but being able to pair a content model with a component and know that I can simply compose it from the CMS without needing to touch code feels extra special.
Below is an initial design idea for the new homepage hero, which is authored from the content model above. Everything’s still a work in progress, but I really like where this is going—and of course, I’m going to animate smoke coming from the house’s chimney.