Building flexible page layouts for Metalsmith

In this article, we will delve into creating flexible page layouts for Metalsmith by utilizing Markdown and Nunjucks templating. For the purpose of this discussion, we will refer to these pages as "Sectioned Pages."

Ordinarily, Markdown pages comprise two main parts: frontmatter and a page body. The frontmatter typically encapsulates metadata elements such as the template name, draft status, SEO title, and description, among others. On the other hand, the page body constitutes the core content, representing a sizable, unstructured Markdown block.

While this format works perfectly for simple blog posts and basic page layouts, it falls short when designing web pages with more intricate structures. This is where sectioned pages come in.

Sectioned pages are entirely composed of structured data within their frontmatter, written in the YAML format, and notably, they don't include a body. The YAML data serves as the page's content model, neatly segmenting the content into distinct components. Each component is defined by a specific set of fields, where a field denotes a YAML key:value pair. These fields can hold short text strings, numbers, booleans, and even Markdown, thanks to YAML's support for multi-line text areas. Each component subsequently utilizes a template that processes these fields.

This methodology empowers us to create reusable templates that can be utilized individually or in groups, anywhere within the site. With predefined layouts and styles, it facilitates a consistent content representation throughout the site.

Page source

What does that look like in practice? Here is the code of the home page of the Metalsmith Company Starter.

index.md

---
layout: sections.njk
bodyClasses: "home"

seo:
  title: Metalsmith Company Starter
  description: "A starter to build a company website with Metalsmith"
  socialImage: "/assets/images/metalsmith-starter-social.png"
  canonicalOverwrite: ""

sections:
  - component: banner
    ...
  - component: intro
    ...
  - component: media
    ...
  - component: media
    ...
  - component: intro
    ...
---

The section of particular interest here is sections. This represents all the section entries for the page and is structured as a YAML list. It begins with a banner component, followed by intro and media components which form the rest of the page. The resulting rendered page appears as follows:

Let's have a look as the banner component in detail.

banner.njk

- component: banner
  disabled: false
  inContainer: true
  marginTop: false
  marginBottom: true
  paddingTop: false
  paddingBottom: true
  backgroundColor: ""
  targetId: ""
  title: Metalsmith Company Starter
  header: "h1"
  subtitle: Using "sectioned" pages to build flexible page layouts
  prose: This starter is build in the style of a company marketing site. The components on this site are bare-bone interpretations of common information presentation patterns that can be found on many corporate websites. [The source code for this site can be found on GitHub](https://github.com/wernerglinka/metalsmith-company-starter).
  hasCtas: true
  ctas:
    - url: "https://github.com/wernerglinka/ms-components"
      label: Get the Starter
      isExternal: true
      isButton: true
      buttonStyle: "primary"
      isVideoTrigger: false
      videoId: ""
  mediaType: Image
  video:
    src: youtube
    id: ""
    tn: ""
    aspectRatio: ""
    caption:
  image:
    src: "v1647546742/tgc2022/corporate_ip4qbt.jpg"
    alt: "Metalsmith Javascript"
    aspectRatio: "56.25"
    caption: "Photo by Josh Hild from Pexels"
  lottieData:
    src: ""
    control:
      autoplay: true
      loop: true
  icon: ""
  audio:
    bgImage: ""
    aspectRatio: ""
    ogg: ""
    mpeg: ""
    caption:

We can easily see how the banner component content model correlates to the rendered page section.

disabled: false

A component can be deactivated, meaning it won't render, but its code can remain intact. This is typically used as a temporary measure. If the component becomes redundant, its code can simply be removed from the project.

inContainer: true
marginTop: false
marginBottom: true
paddingTop: false
paddingBottom: true
backgroundColor: ""

A component has the flexibility to either be enclosed within a container or extend across the full width of a page. By adjusting margins and padding, we can effectively manage the spacing between neighboring page sections. Moreover, it's possible to apply a background color to further enhance the visual layout of the page.

targetId: ""

A component may be the target for in-page anchor links.

title: Metalsmith Company Starter
header: "h1"
subtitle: Using "sectioned" pages to build flexible page layouts
prose: This starter is build in the style of a company marketing site. The components on this site are bare-bone interpretations of common information presentation patterns that can be found on many corporate websites. [The source code for this site can be found on GitHub](https://github.com/wernerglinka/metalsmith-company-starter).

In this case the section title is an h1 as it is also the page title.

hasCtas: true
ctas:
  - url: "https://github.com/wernerglinka/ms-components"
    label: Get the Starter
    isExternal: true
    isButton: true
    buttonStyle: "primary"
    isVideoTrigger: false
    videoId: ""

This particular component can feature multiple call-to-action (CTA) prompts, although in the current instance, it incorporates just one. The CTA could direct users to either an internal or external page, or it could trigger a video modal. Furthermore, it can be displayed in various formats such as a button or a simple text link.

mediaType: Image
  video:
    src: youtube
    id: ""
    tn: ""
    aspectRatio: ""
    caption:
  image:
    src: "v1647546742/tgc2022/corporate_ip4qbt.jpg"
    alt: "Metalsmith Javascript"
    aspectRatio: "56.25"
    caption: "Photo by Josh Hild from Pexels"
  lottieData:
    src: ""
    control:
      autoplay: true
      loop: true
  icon: ""
  audio:
    bgImage: ""
    aspectRatio: ""
    ogg: ""
    mpeg: ""
    caption:

The banner component encompasses a media element that can vary, including an image, a video, an icon, or even a Lottie animation. It's important to note that we specify the aspect ratio for images. This is required for the responsive image component implemented on this site.

Partial elements such as CTAs and media types are shared among various components. However, distinctive styles are applied to the banner via the Nunjucks template.

Templating System

The rendering of the page involves several templates. A default layout.njk template supplies the page container along with the header and footer. Meanwhile, the sections.njk template takes charge of rendering all the components.

layout.njk

<!DOCTYPE html>
<html>
  <head>
    {% include "sections/head.njk" %}
  </head>
</h1>

<body class="{{ bodyClasses }}">
  <div id="at-top"></div>
  {% include "sections/header.njk" %}

  <main>
    {% block body %}
      This is the default contents
    {% endblock %}
  </main>

  {% include "sections/footer.njk" %}
  {% include "partials/scripts.njk" %}

</body>
</html>

sections.njk

{% extends "layout.njk" %}

{% from "./sections/component.njk" import component %}

{% block body %}
  <section class="main-content">

    {% for section in sections %}

      {% set name = section.component %}
      {% set params = section %}
      {% set site = site %}

      <div class="section-wrapper {% if section.inContainer %}inContainer{% endif %}">
        {{ component(name, params, site) }}
      </div>

    {% else %}
      <p class="error-message">There are no sections available</p>
    {% endfor %}
  </section>
{% endblock %}

At the core of this methodology is the sections.njk template. Notably, all components are integrated into the template by a name reference. The template proceeds to loop through all sections, rendering them based on their position in the list. Consequently, a section's position is not hard-coded but rather contingent on its list placement. If you wish to reposition a section, it's as simple as relocating the component code in the page source. When using a CMS like forestry.io, this relocation can be accomplished effortlessly with a drag and drop feature.

To introduce a section, we employ a Nunjucks macro component. Utilizing a macro safeguards the component fields from global scope interference, preventing components with identical fields from overwriting each other's properties.

component.njk

{% macro component(name, params, site) %}
  {% include "../sections/" + name + ".njk" ignore missing %}
{% endmacro %}

The metalsmith-company-starter code is available at https://github.com/wernerglinka/metalsmith-company-starter and the demo site can be viewed at https://metalsmith-company-starter.netlify.app/

For another comprehensive example of this website building technique, you can visit the following link: https://ms-page-sections.netlify.app/. It provides an excellent showcase of how this methodology can be implemented in practical scenarios.

Scroll to top