Layout Management in a Headless CMS

While Agility is a Headless CMS and can boast all the benefits that come with that, there is one area where we take a different approach than others. Layout Management.

Yep, we are opinionated about it and stand by it!

One of the main concepts of a Headless CMS is that the CMS should make no assumptions about how Developers will use it.  This means as an architect you often start with a blank slate and you configure your content architecture exactly to your specifications.

That's great, and something we provide out-of-the-box with Content Models, but there are some limitations to this approach when using a Headless CMS to build websites and it affects both Developers AND Editors.

Let's take a closer look.

Using Content Models to Manage Layout Content

Let's go through some content architecture that's using Content Models for each type of layout and see how it holds up. For simplicity, let's start with a single landing page for an upcoming video game.

Layout: Home

Video game landing page example on agilitycms.com

How would you architect your content for this layout? Likely, you would create a Content Model that represents the content that is editable on this page.

Content Model: Landing Page

The elements of a landing page on agilitycms.com

And here's how it might look in Agility:

Screenshots of Headless CMS on agilitycms.com

And here's what you would get back from the Agility Content Fetch API for this item: 

curl https://046a1a87-api.agilitycms.cloud/fetch/en-us/item/28 
--header "APIKey: defaultlive.2b7f3a91559d794bedb688358be5e13af2b1e3ae8cd39e8ed2433bbef5d8d6ac"
{
  "contentID": 28,
  "properties": {
    "state": 2,
    "modified": "2019-08-16T15:49:56.583",
    "versionID": 173,
    "referenceName": "dodgeblocklandingpage",
    "definitionName": "LandingPage",
    "itemOrder": 0
  },
  "fields": {
    "brandLogo": {
      "label": "Dodgeblock VR",
      "url": "https://046a1a87-cdn.agilitycms.cloud/Attachments/NewItems/dodgeblock_20190816194430_0.png",
      "target": null,
      "filesize": 19767,
      "pixelHeight": "54",
      "pixelWidth": "440",
      "height": 54,
      "width": 440
    },
    "youTubeURL": "https://www.youtube.com/watch?v=TjfY_cBc8DI&t=4s",
    "mainRichText": "<h2 style=\"text-align: center;\">Coming soon...</h2>\r\n<h2 style=\"text-align: center;\">An endless runner, re-imagined in VR.</h2>\r\n<p style=\"text-align: center;\">No two runs are ever the same. How long can you last?</p>",
    "primaryLink": "<a href=\"https://www.reddit.com/r/DodgeblockVR/\" target=\"_blank\">Join the Discussion on Reddit</a>",
    "platformsHeading": "Platforms:",
    "platformsImages": {
      "galleryid": 2
    },
    "footerRichText": "<p>Produced by Vidler Studios, 2019</p>"
  }
}

In your code, you would then create a static layout, consume this content item via the API and then use its properties to populate the content in the HTML you've already scaffolded out. Easy right?

Is there anything wrong with this approach? Well, no. It meets the basic requirements of allowing the editor to change the content and even create versions of this content for other languages.

So what's the big deal then? Well, this site and its content are likely to evolve and grow way beyond a single layout. Keeping the current architecture in mind, let's see what would have to happen to handle these common requests in the below scenarios.

Common Scenarios

Editor: I need to create a new landing page specifically for the press. It will have the exact same content layout as the home page, but just different content.

You would need to go back into your code and add another static layout in your website to handle the routing for your new landing page, then create a new item in the CMS to represent the content for that page, take note of the content ID and then likely spend some time refactoring your code now that you are using it in two different places.

EditorI need to be able to set the SEO properties for each layout such as Meta Tags and Meta Description.

You would need to add these additional fields to the Landing Page content model, go back in your code, read those properties, and output them in the appropriate place.

EditorOn the home page, we want the YouTube video to be BELOW the Main Rich Text, but on our press page we want the YouTube video to remain ABOVE the Main Rich Text.

This is where it gets tricky. Both the home page and press page are using the same content model and HTML template. The editor does not have the ability to set the order of content being rendered on the page, because that's hardcoded in your HTML template. Now you have some decisions to make. Do you hard-code some logic in your website to adjust the order only if the current page is the home page? Or do you split your code altogether and use a slightly different HTML template for each page? Neither option sounds great or scalable, but you have to get it done so you reluctantly choose one, deploy it and move on.

EditorOur CEO doesn't like it, can we have the YouTube video ABOVE the Main Rich Text area on the homepage again?

🤬 Ya... we've all been there.

EditorI need to create a new page for our online leaderboards, it needs to show the top 10 players, but also provide a detailed leaderboard where people can explore the entire list.

Alright, sounds fun. You get to play around with the Oculus Leaderboards API, create a new content model for this new type of page, create a new static layout route, and mess around with some responsive tables. A few coffees later, and a sleepless night, and you get it done. This new leaderboard page now has two main components on the page, one is the top leaders, and the other is the full leaderboard.

Top and full leaders on agilitycms.com

EditorOh, can we have the top leaders on our home page as well?

Ok, that didn't come up as a requirement during discussions, but that's fine. It's not too complicated to extract that functionality into a reusable component. So you do just that, you spend some time refactoring your code and then you add it to your landing page HTML template and deploy your update.

EditorThe top leaders are showing on the press page, we don't want it to show there.

Oh, right! It's using a shared HTML template between home and press page. You now need to handle that logic in your code as well, further complicating your solution.

Limitations

As you can see in the above scenarios, the editor's needs are impossible to predict. It's not their fault, its the nature of the game. So what are the problems here with our architecture?

It's not flexible for the editor, so the developer spends most of their time taking orders, tinkering with existing code, and wishing they were doing something else!

So what does that ultimately mean?

  • Editors cannot create/manage layouts on their own without a developer
  • Editors cannot control which components are on each layout
  • Developers get bored or burnt out
  • More development resources/expenses required
  • Productivity on the website suffers

And, who's at fault in this? I'll give you a hint, it's not the editor and its not the developer... It's the architecture! 

A Better Way: Integrated Layout Management

We always encourage developers and architects to strive to use the best tool for the job. While setting up simple content models to represent your page content can be quick and easy, it doesn't always scale well. To address this, Agility has built-in Layout Management

Using Layout Management, you can empower editors to create and manage layouts for your app using re-usable building blocks (i.e. Component Models). Editors can manage your site's page tree, page-level SEO properties, and determine what content and functionality will be on each page. As a developer and architect, you still have the full control over what components are available to the editor, where they can place them on the layout, and what they do.

Benefits

  • Empowered editors who can do more without a developer
  • Happier developers who can focus on new functionality and enhancements, and less time responding to new content requirements
  • Increased productivity
  • Fewer resources/expenses required

What's in a Layout?

Layouts consist of the following core components:

  • Layout - name, url, seo properties, and other layout-level properties
  • Layout Model - the template that defines zones where editors can place components
  • Zones - the areas on the layout in which editors can add components
  • Component Models - the functional components the editor can add to each zone

Example Layout

Here we have a page that is using a Page Template with a single Content Zone. This allows editors to add/remove/re-order any component to this layout and have each component rendered, one on top of the other. 

On the website, an API call is made to fetch the layout, its zones, and its components.

curl https://046a1a87-api.agilitycms.cloud/fetch/en-us/page/2 
--header "APIKey: defaultlive.2b7f3a91559d794bedb688358be5e13af2b1e3ae8cd39e8ed2433bbef5d8d6ac"
{
  "pageID": 2,
  "name": "home",
  "path": null,
  "title": "Home",
  "menuText": "New Home Text",
  "pageType": "static",
  "templateName": "One Column Template",
  "redirectUrl": "",
  "securePage": false,
  "excludeFromOutputCache": false,
  "visible": {
    "menu": true,
    "sitemap": true
  },
  "seo": {
    "metaDescription": "",
    "metaKeywords": "",
    "metaHTML": "",
    "menuVisible": null,
    "sitemapVisible": null
  },
  "scripts": {
    "excludedFromGlobal": false,
    "top": null,
    "bottom": null
  },
  "properties": {
    "state": 2,
    "modified": "2019-08-01T14:26:01.177",
    "versionID": 48
  },
  "zones": {
    "MainContentZone": [
      {
        "module": "Jumbotron",
        "item": {
          "contentID": 12,
          "properties": {
            "state": 2,
            "modified": "2019-08-01T14:26:02.553",
            "versionID": 135,
            "referenceName": "home_jumbotron",
            "definitionName": "Jumbotron",
            "itemOrder": 0
          },
          "fields": {
            "title": "Blog Post Template ",
            "subTitle": "Welcome to Agility!"
          }
        }
      },
      {
        "module": "RichTextArea",
        "item": {
          "contentID": 22,
          "properties": {
            "state": 2,
            "modified": "2019-08-01T14:26:03.603",
            "versionID": 136,
            "referenceName": "home_richtextarea",
            "definitionName": "RichTextArea",
            "itemOrder": 0
          },
          "fields": {
            "textblob": "<h1>About this Site</h1>\n<p>This is a sample blog that showcases how you can use React and the JS SDK to build a dynamic Single-Page-Application.</p>\n<p><a href=\"https://github.com/agility/agility-create-react-app\" target=\"_blank\" rel=\"noopener\">View the source code</a></p>"
          }
        }
      },
      {
        "module": "PostsListing",
        "item": {
          "contentID": 23,
          "properties": {
            "state": 2,
            "modified": "2019-08-01T14:26:04.837",
            "versionID": 137,
            "referenceName": "home_postslisting",
            "definitionName": "PostsListing",
            "itemOrder": 0
          },
          "fields": {
            "title": "Posts",
            "posts": {
              "referencename": "posts"
            }
          }
        }
      }
    ]
  }
}

The website then parses the response and renders the modules in that order.

Website that renders modules on agilitycms.com

Layout become even more powerful when you implement dynamic routing. You can do this in your app by requesting the sitemap from Agility and match the incoming requested URL.

curl https://046a1a87-api.agilitycms.cloud/fetch/en-us/sitemap/flat/website
--header "APIKey: defaultlive.2b7f3a91559d794bedb688358be5e13af2b1e3ae8cd39e8ed2433bbef5d8d6ac"
{
  "/home": {
    "title": "Home",
    "name": "home",
    "pageID": 2,
    "menuText": "New Home Text",
    "visible": {
      "menu": false,
      "sitemap": false
    },
    "path": "/home",
    "redirect": null,
    "isFolder": false
  },
  "/posts": {
    "title": "Posts",
    "name": "posts",
    "pageID": 3,
    "menuText": "Posts",
    "visible": {
      "menu": false,
      "sitemap": false
    },
    "path": "/posts",
    "redirect": null,
    "isFolder": false
  },
  "/posts/sample-post": {
    "title": "Sample post",
    "name": "sample-post",
    "pageID": 6,
    "menuText": "Sample post",
    "visible": {
      "menu": false,
      "sitemap": false
    },
    "path": "/posts/sample-post",
    "redirect": null,
    "isFolder": false,
    "contentID": 27
  },
  "/posts/how-this-site-works": {
    "title": "How this site works!",
    "name": "how-this-site-works",
    "pageID": 6,
    "menuText": "How this site works!",
    "visible": {
      "menu": false,
      "sitemap": false
    },
    "path": "/posts/how-this-site-works",
    "redirect": null,
    "isFolder": false,
    "contentID": 15
  },
  "/posts/how-to-handle-seo-with-a-js-app": {
    "title": "How to handle SEO with a JS app",
    "name": "how-to-handle-seo-with-a-js-app",
    "pageID": 6,
    "menuText": "How to handle SEO with a JS app",
    "visible": {
      "menu": false,
      "sitemap": false
    },
    "path": "/posts/how-to-handle-seo-with-a-js-app",
    "redirect": null,
    "isFolder": false,
    "contentID": 16
  }
}

From the above response, you can easily determine if an incoming request matches a Layout in Agility. If it does, then you would make the appropriate Get Page API call to get the layout's content, and lastly pass the fields for each component to a component in your web app who will be responsible for rendering that content.

If build your web app to support layout routing and component rendering, you'll find that your editors can do more, and you'll be able to spend more time building cool stuff.