This blog post presents some perspectives on data modeling to support a simple, generic web page with the Contentstack SaaS headless CMS. This topic considers site data (visible on almost every page), shared data (visible on multiple pages), metadata at the page level, and page data. If you know of additional categories of data or have any other perspectives on this topic, please comment on this blog post.
Quickly, some terminology:
- Entry: A record in the CMS consisting of several of field values. Page entries represent pages that have URLs. Other records store data that may be used by pages, but itself does not have a URL.
- Content Type: Defines the fields that appear in a type of entry.
- Data Model: An entry, or the fields of an entry, designed to suit a specific purpose, such as a content type to define the fields for a specific type of page or a collection of fields designed to populate a category of presentation components.
- Page Model: The data model for a web page.
- Modular Blocks: A Contentstack feature for rich data models.
- Data Modeling: Defining content types, entries, and the relationships between entries.
I use the term global group where Contentstack uses the term global field, because a global field is functionally a global group that can contain any number of fields. A global group is a structure that defines a collection of fields that multiple content types can reference, effectively repeating the structure definition as a group within each of those content types.
Assume that every URL corresponds to an entry in the CMS, where that entry may reference additional entries from which to retrieve additional data when rendering the page.
CMS users mange different types of data for any page:
- Site Data: Shared to almost every page on the site, such as a main navigation and a footer.
- Shared Data: Used by multiple, but not all pages on the site, such as calls to action in a sidebar.
- Metadata: Page metadata, such as the title, search and SEO, OpenGraph, and other characteristics of the page managed by CMS users.
- Page Data: Content specific to the page, such as its body text or a breadcrumb.
I believe that every data element should have a primary URL, as otherwise it cannot appear in search results. Where possible, I prefer to minimize content types by modeling most data in a linear fashion within content types that define pages rather than by creating additional content types that define content fragments and relationships between entries.
Almost all pages share site data, such as the main navigation or the footer, that is managed in the CMS. Where possible, such as with data-driven navigation, retrieve site data from existing entries rather than implementing additional content types. For example, rather than implementing a content type to define the top navigation, consider using the information architecture of the website to drive navigation dynamically.
- SaaS Headless CMS ASP.NET Core Razor Pages Data-Driven Top Navigation View Component – Deliverystack.net
If the solution requires entries that contain data specifically to control site elements such as the main navigation or the footer, then model that data with content types separate from those used to model pages. Content types that model pages may contain fields that reference site data entries. For example, the entry that represents the home page can reference an entry that defines the footer for the site, and all pages can follow that reference when they need to render the footer, unless the content type for that page contains an equivalent field that allows it to override the site footer. Avoid storing the data for the footer in the entry for the home page itself and potentially any entries that need to override the footer.
Multiple pages, but not all pages on the site, can reuse shared data, such as calls to action in a sidebar. Whenever possible, model shared data in the content type for pages to which it can belong, so that the data has a URL. If different types of pages use the same types of shared data, then implement the data models as global groups (also called global fields).
Many calls to action link to pages. Store the data for the calls to action in the entries for those pages, not in the pages that contain the calls to action, and preferably not in separate entries for those page entries to reference, where the referenced entries contain data for the call to action including a reference to the page that is the target of that call to action. Consider implementing the fields for the call to action as a global group, potentially repeating within one or more content types.
Every page contains a significant amount of metadata used by browsers, search engines, social media systems, and for other purposes. In Contentstack, use a global group to model metadata for all pages, and use that global group in all content types that define pages. In this way, CMS users have a consistent experience for metadata entry, and developers have a consistent format for metadata retrieval.
Page data is anything that is specific to an individual page and that does not belong in one of the other categories. Typically, this means the main content of the page. While it may be preferable to reference separate entries, the content type for a page can include fields to store data for any use on the page, such as to control and populate presentation components, store URLs for posting forms, or for any other purpose.
Pages can have incredibly simple page models that consist of only a few fields. Avoid overreliance on rich text fields including HTML, JSON, and markdown editors in favor of separating the data into multiple fields.
Other pages are more complex, sometimes requiring a very large number of fields. To improve usability, use reference fields and modular blocks to avoid implementing a large number of fields that only might be used by a page. Note that using reference fields to separate data into multiple entries require more consideration and effort for translation, workflow, publishing, and otherwise, as well as having an impact on developers. Remember to implement any reusable field structure as a global group.
One way to provide unlimited flexibility in a page is to implement a content type for each presentation component that may appear on the page, and in the content type for the page, a reference field to select any number of entries based on those content types. For example, if a page could contain some number of rich text fields and some number of videos, then implement a content type for each, and a reference field in the content type for the page to let the CMS user select any number of rich text and video entries. Then, implement a presentation component that iterates, retrieves, and renders the referenced entries using the appropriate nested presentation components. If possible, implement the referenced entries as page entries rather than fragment entries.
Alternatively, consider modular blocks, which have numerous advantages for CMS usability and developer productivity. Rather than implementing a content type to manage data for each category of presentation components, implement block models within a modular blocks field. In the presentation component, rather than iterating referenced entries, iterate the blocks in the modular blocks field. Note that individual blocks do not have URLs.