7.1 More ways to layout content #
There are multiple types of pages in an application, each with a different look. A blog page, for example, typically has a sidebar with additional information about the blog post. Various types of pages can have different CSS styles and different HTML structures to accommodate their unique design. One way to style pages differently, which we used in chapter 6, is to use a new content type for each category of page created. While content types are potent concepts that provide a mechanism to isolate the rendering code for a particular type of content, there are additional ways in Hugo to render content in a different design—parameterized front matter or a new layout. All three approaches have their use cases, advantages, and disadvantages, and we can use all of them in tandem in the same website. For the Acme Corporation website, the web page design we used for the Terms of Use page (section 6.1.4) is pretty barebones. It does not work well with the content pages in the blog and the news sections. We need a more elaborate design for these pages. The desired changes in the News page can be achieved by CSS, while for the Blog pages, we want to add a sidebar, which involves adding HTML content. Let’s see the various ways in which we can achieve this with Hugo.
7.1.1 Parameterizing front matter to differentiate the News page interface #
When we have minor HTML changes between two different types of pages, we might not want to create a new content type. We can expose a variable that the template code can use to provide differentiated HTML. Using the conditional statements if and else, we can write different HTML content if needed. This way, minor HTML changes live in the same file, and we do not need to manage a separate file on disk. We can have maximum code reuse, and we do not need to split the code into partials to prevent repetition. For the News pages on the Acme Corporation website, we do not need many changes. These pages come with cover images that we need to apply. Remember from chapter 6, the .Resources in the context of a page refers to the resources in the page bundle for that page. We can use a with or an if check to ensure that if the resources are not present, no additional HTML is generated. We can then add this to the existing single.html as in the following listing.
Listing 7.1 A cover image for the News page (layouts/modern/single.html)
The pages in the news section (figure 7.2) in the Acme Corporation website have a similar HTML structure to the pages we have developed so far, and all the changes live in the CSS styling. Because the HTML is mostly the same, a conditional statement approach to switch CSS classes is better suited for this use case. Although we can take the class name from the front matter as a variable and use that by calling the $.Param function, there are ways with which we could get this information without needing to write anything in the content area of the website. Had we not set up the content type, we could have used the content type for this case via the .Type property.
Figure 7.2 The news sections are rendered with a different UI by switching the CSS styling using the section information. (Image by tookapic on Pixabay.)
We can use the folder location of the page to find its section. If we run {{path.Base (path.Dir .Page.FirstSection.File.Path)}} for the News page, we will get the news section. .Page.FirstSection points to the _index page of the news section, and we can find the folder name using the path.Dir at its path. Note that we can also use CurrentSection for the News page, but this will break the community subsection of the blog section. We can use this folder location to change the HTML class of the News page and make it different from the other pages as in listing 7.2.
TIP Minimizing the need for entering data in the front matter can help keep the content area simple. When creating content, the less we have to think about front matter metadata, the better. We make a theme once, but we continuously add content to the website.
Listing 7.2 Adding the page section name (layouts/modern/single.html)
{{define "bodyClass"}}
{{ path.Dir .Page.FirstSection.File.Path) }} page
{{end}}
TIP It is good to expose one entry in the head section of the website in the define block. This can be overridden for page-specific metadata or for supplying page-specific CSS/JavaScript files, making the theme extensible. We could even have a naming convention like cover.* to load additional CSS/JS files.
The single page template can automatically find the section’s name and provide the appropriate CSS class with these changes. Because this code is present in the single page template for the modern type in Hugo, we need to assign all pages in the news section to this content type. We will need to add type: modern to every page in the news section. Considering there could be hundreds of News pages, that is a lot of work. For new pages, however, archetypes (discussed in section 5.4.3) can help in predefining them, but the text will still be required, leading to the possibility of typographical errors and other mistakes.
7.1.2 Using the cascade property to apply properties to the front matter of multiple pages #
One central principle in Hugo is to reduce work and prevent duplication of the code to the maximum extent possible. Having a property shared between all pages in a section is common for websites. Hugo recognizes this and provides a mechanism to write this property once and apply it to all of the pages. Hugo has a property in the front matter called cascade (figure 7.3), whose subproperties apply as front matter properties to all pages in that section.
Figure 7.3 The cascade property allows us to provide values to the front matter variables, which we can use across the whole section. In the diagram, local specifies the keys in the front matter, while cascade sets the same key as a subkey for the cascade. The top-level _index file specifies a cascade that applies to that file as well as to the subsections for its child pages. /C issues a cascade override so that its subtree gets the cascade value from this section. /C2 does not use an override, and therefore, the cascade from /C carries through. In /C2b, we have a local override that takes over the property. In /D, we do not override the cascade property, but /D2 does a local override. /E has both a local override for the specific index page and a cascade for the child pages, which /E1 uses. Section /F only has a local override, and the properties from its root follow through to /F1.
The concept of cascade is the same as in CSS, which stands for cascading style sheets. We define a generic property at the top level and then a specific property when an override is needed. The more specific property overrides the generic one. We can even do a local override, where the cascade property applies to the subpage but not to the current page because the property present in the front matter for the current page has a higher priority.
On the _index page for the news section, let’s enable the type modern via the cascade property as in listing 7.3. We will revert it for the news/_index page because the modern type does not have the HTML layout for a branch list page. By default, the cascade property applies to all pages in a section, including subsection index pages (list.html) and individual (single.html) pages.
Listing 7.3 The modern type in all content pages (content/news/_index.md)
cascade: type:
modern
type: _default
NOTE We could have used a garbage string in the front matter’s root level type field. Hugo falls back to the _default folder to look for the layout if the type specified in the front matter is not present on the disk.
CODE CHECKPOINT https://chapter-07-01.hugoinaction.com, and source code: https://github.com/hugoinaction/hugoinaction/tree/chapter-07-01.
Every page in the news section has been assigned to the content type modern using the cascade property. Once this is enabled, we can see the child pages in the news section render with the new CSS styling for the News pages. The next set of pages that need a different design are the Blog pages.
NOTE We can still override the cascade property by manually specifying it in the front matter; for example, if we put type: _default in any of the pages in the news section, that specific page renders with the default content type even if it is in the news section that has set the cascading content type modern. We can view the various permutations of cascade and overrides in figure 7.3.
7.1.3 Providing a different layout to the blog content #
The design for the blog section includes a sidebar (figure 7.4). Over time, we expect blogs to diverge in design from the News pages or the regular web pages. Keeping the code together with unrelated pages is not a scalable solution. When we have more than a couple of conditional statements, things can get complicated, especially if the content in these conditional statements is enormous. If we want to share the base template with different generated HTML for various content, we need to create a new layout.
Figure 7.4 The Blog page with a sidebar was created by adding a new layout to the modern content type web pages. (Image by Pexels on Pixabay.)
The default layout for a web page/leaf bundle is the single layout, so we have a file called single.html. This is where we provide the overrides to the base template. But we are not restricted to that layout. We can override it to anything we desire. Layouts within a content type share all templates within the content type including render hooks. Table 7.1 compares the content type with a layout.
Table 7.1 Content type vs. layout
We want to have a sidebar in the blog section to provide more information about the blog post. We can provide information like tags in the front matter and things like summaries used in the index page and other properties in the front matter. Along with this, Hugo can generate a bunch of other metadata such as an approximate reading time with the .ReadingTime property and an approximate word count with the .WordCount property.
Let’s create a new file called blog.html in the modern folder (https://github.com/hugoinaction/hugoinaction/tree/chapter-07-resources/01). In this file, we will define the body block for the Blog pages to include a sidebar. The following listing sets up a sidebar in the HTML