mrkaluzny homepage
Tips & Tricks

DRY Decap CMS config with Manual Initialization

Feb 25, 2021

In 2019 I absolutely fell in love with JAMStack and static websites. My love blossomed since I've encountered Gatsby. For a lot of websites out there, there is simply no better way to do create a website. Both this website and my company's - Clean Commit - are using Gatsby with Decap CMS.

Static websites are steadily growing as a percent of projects we work on. Having experience using Gatsby paired with Decap CMS I found a couple tweaks that should optimize your development experience with CMS configuration.

Decap CMS's YAML configuration can quickly become a mess

If you had the pleasure (or rather, displeasure) of working with fairly complex content in Decap CMS you know the config.yml file can grow quickly. One of the projects I worked on was a website for Dionysus - a company that specializes in organizing cultural events. This website contains 100s of events that are interconnected. At one point events were separated into different content types. Maintaining that CMS setup with YAML content quickly turns into a mess.

How to solve this issue? Manual Initialization!

Easy. Instead of YAML, we can use JavaScript to configure Decap CMS with Manual Initialization. Setting up manual initialization is easy.

To enable manual initialization you need to create a JS file with the configuration and let Decap CMS know to use it.

{
	resolve: 'gatsby-plugin-netlify-cms',
		options: {
			manualInit: true,
	    modulePath: `${__dirname}/src/cms/cms.js`,
	  },
	},
}

I strongly suggest keeping all configuration files within src/cms to keep them well organized.

To use Decap CMS with manual initialization, you also need to set up the general CMS settings.

import CMS from 'netlify-cms-app';
import pages from '@/cms/pages';
import posts from '@/cms/collections/posts';

window.CMS_MANUAL_INIT = true;

CMS.init({
  config: {
    load_config_file: false,
    backend: {
      name: 'git-gateway',
      branch: 'master',
    },
    media_folder: '/static/img',
    public_folder: '/img',
    collections: [pages, posts],
  },
});

Organizing posts and pages with Decap CMS

When using manual initialization with Decap CMS I divide settings into 3 groups - pages (for unique pages), collections, and partials. Sometimes we need to add additional groups like settings.

Setting up pages is mostly straight-forward. The main file I use to control pages is src/cms/pages/index.js.

import home from '@/cms/pages/home';
import blog from '@/cms/pages/blog';
import about from '@/cms/pages/about';
import privacy from '@/cms/pages/privacy';

const pages = {
  name: 'pages',
  label: 'Pages',
  files: [home, about, blog, privacy],
};

export default pages;

The main pages file is used to organize page order in the CMS and loading new pages.

import seo from '@/cms/partials/seo';
import SmallHero from '@/cms/partials/sections/small-hero';

const page = {
  file: 'content/pages/home.md',
  label: 'Home',
  name: 'Home',
  fields: [
    {
      label: 'Layout',
      name: 'layout',
      widget: 'hidden',
      default: 'contact',
    },
    {
      label: 'Type',
      name: 'type',
      widget: 'hidden',
      default: 'page',
    },
    {
      label: 'Title',
      name: 'title',
      widget: 'string',
      default: '',
      required: false,
    },
    SmallHero,
    seo,
  ],
};

export default page;

Keep Decap CMS configuration DRY with partials

Every page file contains configuration for individual fields and uses partials to provide fields that are used across different collections/pages. The example above shows one section that is reused called SmallHero. The second most common partial we use is the seo partial. This partial provides metadata information for each page and collection item.

const seo = {
  label: 'SEO Settings',
  name: 'seo',
  widget: 'object',
  collapsed: true,
  fields: [
    {
      label: 'Title',
      name: 'title',
      widget: 'string',
      required: false,
    },
    {
      label: 'Meta Description',
      name: 'description',
      widget: 'text',
      required: false,
    },
    {
      label: 'Image',
      name: 'image',
      widget: 'image',
      required: true,
      default: '/img/shareable-default.jpg',
    },
  ],
};

export default seo;

With partials, you don't have to edit multiple files to make changes across collections or pages.

Complex data structures with Decap CMS

I love ACF when working with WordPress. A lot of our projects since 2018 are made using Flexible Content from the Advanced Custom Fields plugin. It gives great flexibility for page creation for end users without the need for the developer's input. Headless CMSs started to adopt that feature, Prismic has Slices, Butter CMS has Components and Decap CMS has lists.

For more on complex content solution read this article on recreating Flexible Content field with Decap CMS & Gatsby

Lists support types params that can enable you to create flexible content fields. I wouldn't try that without manual initialization. That old YAML file would grow enormous quickly. Not to mention moving types across different pages.

import seo from '@/cms/partials/seo';

import SmallHero from '@/cms/partials/sections/SmallHero';
import DarkSection from '@/cms/partials/sections/DarkSection';
import Perks from '@/cms/partials/sections/Perks';
import Pointers from '@/cms/partials/sections/Pointers';
import Testimonials from '@/cms/partials/sections/Testimonials';

const collection = {
  name: 'services',
  label: 'Services',
  editor: {
    preview: false,
  },
  description: 'Service content',
  folder: 'content/services',
  slug: '{{slug}}',
  create: true,
  fields: [
    {
      label: 'Type',
      name: 'type',
      widget: 'hidden',
      default: 'service',
    },
    {
      label: 'Layout',
      name: 'layout',
      widget: 'hidden',
      default: 'Service',
    },
    {
      label: 'Title',
      name: 'title',
      widget: 'string',
      required: true,
    },
    {
      label: 'Featured Image',
      name: 'thumbnail',
      widget: 'image',
      required: false,
    },
    {
      label: 'Sections',
      name: 'sections',
      widget: 'list',
      types: [SmallHero, DarkSection, Perks, Pointers, Testimonials],
    },
    seo,
  ],
};

export default collection;

The example above showcases how I created service pages on Clean Commit's website.

Here's an example section - SmallHero

const smallHero = {
  label: 'Small Hero',
  name: 'hero',
  widget: 'object',
  collapsed: false,
  fields: [
    {
      label: 'Title',
      name: 'title',
      widget: 'string',
      required: false,
    },
    {
      label: 'Header',
      name: 'header',
      widget: 'string',
      required: false,
    },
    {
      label: 'Content',
      name: 'content',
      widget: 'markdown',
      required: false,
    },
  ],
};

export default smallHero;

Types with Manual Initialization is a perfect match for complex website content, making it possible to use Decap CMS on both small and medium projects efficiently. Moving configuration between projects is also a breeze.

Key takeaways for keeping Decap CMS configuration DRY

When your project gets large, use Manual Initialization instead of YAML configuration, it's easier to maintain.

Divide your Decap CMS configuration into 3 base groups - pages, collections, and partials. Fields shared across different entities should become partials.

For flexibility in content creation combine manual initialization with Decap CMS types using List widget. - For more on this topic read this article on recreating Flexible Content field with Decap CMS & Gatsby

For more helpful tips check out the Tips & Tricks category on my blog