Web Studio is here! An enhanced experience to make it easier to create, preview, and collaborate on your website contentLearn More
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. Page 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.
Let's go through some content architecture that's using Content Models for each type of page and see how it holds up. For simplicity, let's start with a single landing page for an upcoming video game.
Page: Home
How would you architect your content for this page? Likely, you would create a Content Model that represents the content that is editable on this page.
Content Model: Hero
Here's how it might look to a Content Editor in Agility:
And here's what you would get back from the Agility Content Fetch API for this page:
curl -X GET "https://api.aglty.io/f294e59a-u/fetch/en-us/page/2?contentLinkDepth=3" -H "accept: application/json" -H "APIKey: defaultlive.f39c27a1e7df3fc6fd1fd310451ca8cfccf135b514bf4be0bf8536cf8cfa72b0"
{
"pageID": 2,
"name": "home",
"path": null,
"title": "Home",
"menuText": "Home",
"pageType": "static",
"templateName": "Main Template",
"redirectUrl": "",
"securePage": false,
"excludeFromOutputCache": false,
"visible": {
"menu": true,
"sitemap": true
},
"seo": {
"metaDescription": "Travel blog featuring travel tips, guides, and photography from around the world.",
"metaKeywords": "Travel, Blog, Flights, Vacation, Hotels, Trip, Travel Agency, Airlines, Plane Tickets",
"metaHTML": "",
"menuVisible": null,
"sitemapVisible": null
},
"scripts": {
"excludedFromGlobal": false,
"top": null,
"bottom": null
},
"properties": {
"state": 2,
"modified": "2024-11-06T15:04:23.62",
"versionID": 199
},
"zones": {
"MainContentZone": []
}
}
In your code, you would then create a static page, 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 page. Keeping the current architecture in mind, let's see what would have to happen to handle these common requests in the below scenarios.
Editor: I need to create a new landing page specifically for the press. It will have the exact same content page as the home page, but just different content.
You would need to go back into your code and add another static page 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.
Editor: I need to be able to set the SEO properties for each page 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.
Editor: On 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.
Editor: Our 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.
Editor: I 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 page 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.
Editor: Oh, 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.
Editor: The 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.
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?
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!
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 Page Management.
Using Page Management, you can empower editors to create and manage pages 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 page, and what they do.
Pages consist of the following core components:
Here we have a page that is using a Page Model with a single Content Zone. This allows editors to add/remove/re-order any component to this page and have each component rendered, one on top of the other.
On the website, an API call is made to fetch the page, 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 components in that order.
Page 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 Page in Agility. If it does, then you would make the appropriate Get Page API call to get the page'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 page 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.