How the Nuxt Blog Starter Works

The Blog Starter showcases features such as our native Page Management and shows how you should structure your Nuxt website. This starter serves an example based on our recommended best-practices.

We believe that Editors (you know, the folks actually creating content) should have full control over their website pages and not rely on a developer.

The @agility/agilitycms-nuxt-module makes it easy to source content, but it also assists with generating your Pages for you based on your sitemap in Agility CMS.

This means that editors in the CMS control what pages are available, what their URLs are, and exactly what UI components (we call these Page Modules) make up each page.

The following example is based on the Blog Starter instance. Don't have one? Sign up for one - https://agilitycms.com/free

Routing and Pages

Page Generation

Routing pages with Agility CMS

The above figure represents a sitemap of Pages as well as the UI Components (Page Modules) that are on each page - all are managed in the CMS. 

This means that when a build occurs in your Nuxt site, the following pages will be auto-generated for you:

/
/blog
/blog-posts/* --your dynamic page route for all of your blog posts (i.e. /blog/my-first-post)
/about

Page Rendering

When a Page is being generated, the final step is rendering the page to HTML. The agilitycms-nuxtjs-starter does this by using the AgilityPage.vue component that is configured in the nuxt.config.js file.

This component takes care of generating pages based on your Sitemap in Agility, determines which Page Template to render, and handles your SEO properties.  

nuxt.config.js:

export default {
  target: "static",
  components: true,
  generate: { fallback: "404.html" },
  modules: [
    "@nuxtjs/tailwindcss",
    "@agility/agilitycms-nuxt-module",
  ],
  agilitycms: {
    channelName: "website",
    languages: ["en-us"],
    includeLanguageCodeInUrl: false,
    pageComponentPath: "src/AgilityPage.vue",
  },
  ...
};

AgilityPage.vue:

<template>
  <div
    v-if="statusCode !== 200"
    class="max-w-screen-xl mx-auto p-4 sm:p-6 lg:p-6"
  >
    <div class="my-6 max-w-full">
      <div v-if="message">{{ message }}</div>
      <div v-else>The page could not be found, or an error occurred.</div>
    </div>
  </div>
  <div v-else>
    <component
      :is="componentToRender"
      :page="page"
      :pageInSitemap="pageInSitemap"
      :dynamicPageItem="dynamicPageItem"
      :moduleData="moduleData"
    />
  </div>
</template>

<script>
import AgilityComponents from "./agility.components";

export default {
  data() {
    return {
      pageInSitemap: { title: "" },
      page: { title: "", seo: { metaDescription: "", keywords: "" } },
      dynamicPageItem: null,
      moduleData: {},
      message: null,
      statusCode: 0,
    };
  },
  computed: {
    componentToRender: function() {
      const component =
        AgilityComponents.pageTemplateComponents[this.page.templateName];
      return component;
    },
  },
  mounted: function() {},
  head() {
    return {
      title: `${this.pageInSitemap.title} | My Travel Blog`,
      meta: [
        {
          hid: "generator",
          name: "generator",
          content: "Agility CMS",
        },
        {
          hid: "agility_timestamp",
          name: "agility_timestamp",
          content: new Date().toLocaleString(),
        },
        {
          hid: "viewport",
          name: "viewport",
          content: "initial-scale=1.0, width=device-width",
        },
        {
          hid: "description",
          name: "description",
          content: this.page.seo.metaDescription,
        },
        {
          hid: "keywords",
          name: "keywords",
          content: this.page.seo.metaKeywords,
        },
      ],
    };
  },

  async asyncData(context) {
    if (process.server) {
      const {
        app,
        store,
        route,
        params,
        query,
        env,
        isDev,
        isHMR,
        redirect,
        error,
        $config,
        $agility,
      } = context;

      try {
        let slug = params.pathMatch;
        if (slug == "/") slug = "";

        if (slug.length > 1 && slug.lastIndexOf("/") == slug.length - 1) {
          slug = slug.substring(0, slug.length - 1);
        }

        let languageCode = $agility.languages[0];

        const sitemap = await $agility.client.getSitemapFlat({
          channelName: $agility.channelName,
          languageCode,
        });

        const path = `${slug}` || Object.keys(sitemap)[0];
        let pageInSitemap = sitemap[path];

        if (!pageInSitemap) {
          const message = `Page not found on sitemap in ${languageCode}.`;
          error({ statusCode: 404, message });
          return { statusCode: 404, message };
        }

        let page =
          (await $agility.client.getPage({
            pageID: pageInSitemap.pageID,
            languageCode: languageCode,
          })) || null;

        if (!page) {
          const message = `Page not found in ${languageCode}.`;
          error({ statusCode: 404, message });
          return { statusCode: 404, message };
        }

        let dynamicPageItem = null;

        if (pageInSitemap.contentID > 0) {
          dynamicPageItem = await $agility.client.getContentItem({
            contentID: pageInSitemap.contentID,
            languageCode: languageCode,
          });
        }

        let moduleData = {};
        //load extra data

        for (let zoneName in page.zones) {
          let zone = page.zones[zoneName];
          for (let moduleIndex = 0; moduleIndex < zone.length; moduleIndex++) {
            let module = zone[moduleIndex];
            const moduleName = module.module;

            //try to find a data accessor for this module name...
            const fetcher = AgilityComponents.dataFetch[moduleName];
            if (fetcher) {
              moduleData[moduleName] = await fetcher({ $agility });
            }
          }
        }

        return {
          pageInSitemap,
          page,
          dynamicPageItem,
          moduleData,
          message: null,
          statusCode: 200,
        };
      } catch (err) {
        console.error(err);
        error({ statusCode: 500, message: `Error occurred on server.` });
        return { statusCode: 500, message: `Error occurred on server.` };
      }
    }
  },
};
</script>

Page Modules

Page Modules are the functional components that make up a page. Editors use these to compose what type of content is on each page and in what order they appear.

Developers define what page modules are available in the CMS and what fields they have. Each module defined in Agility CMS should correspond to a Vue Component in your Nuxt.js site.

Generally speaking, if you don't have any modules defined or editors don't add modules to pages, then there won't be anything to output for your pages.

What is in a Page Module?

A Page Module in Agility CMS has a name, description, and fields.

The Name is a representation of the module's intended functionality, such as Posts Listing or Rich Text Area.

It is recommended that each module gets a description, this helps users understand when and where to use this definition.

Fields represent the content that can be managed by editors within the module. These fields are then used in code (passed as props) to display the content the editor intended.

Example

In the agilitycms-nuxtjs-starter site, the name of the page module is used to find a corresponding Vue component that matches the same name. If a match is found, that component is dynamically imported and rendered.

For example, if a module has a reference name of RichTextArea in the CMS, then while the page is being rendered, it will look for ./src/components/agility-pageModules/RichTextArea.vue in the ./src/components/agility-pageModules directory.

Nuxt blog with Agility CMS

Internally, the <AgilityContentZone /> component will dynamically import the module and pass all the field values for that module as props.

// RichTextArea.vue

<template>
  <div class="relative px-8">
    <div class="max-w-2xl mx-auto my-12 md:mt-18 lg:mt-20">
      <div class="prose max-w-full mx-auto" v-html="item.fields.textblob" />
    </div>
  </div>
</template>

<script>
export default {
  props: {
    contentID: Number,
    item: Object,
    page: Object,
    pageInSitemap: Object,
    dynamicPageItem: Object,
  },
};
</script>

How to Add a new Page Module

If you create a new Page Module within Agility CMS, you'll want to create the corresponding Vue component for it within the .src/components/agility-pageModules directory.

All of the Page Modules that are being used within the site need to be imported into the agility.components.js file within the .src directory and added to the moduleComponents object:

// Our Agility Modules
import TextBlockWithImage from "./components/agility-pageModules/TextBlockWithImage";
import RichTextArea from "./components/agility-pageModules/RichTextArea";
import Heading from "./components/agility-pageModules/Heading";
import FeaturedPost from "./components/agility-pageModules/FeaturedPost";
import PostsListing from "./components/agility-pageModules/PostsListing";
import PostDetails from "./components/agility-pageModules/PostDetails";

export default {
  dataFetch: {
    ...
  },
  moduleComponents: {
    TextBlockWithImage,
    RichTextArea,
    Heading,
    FeaturedPost,
    PostsListing,
    PostDetails,
  },
  pageTemplateComponents: {
    ...
  },
};

If there is no Vue component for your module, then nothing can be rendered for it in your Nuxt site.

How to Change the Fields

You can alter your fields at any time and the field values passed to your component will update automatically the next time you run another build.

Fetch Additional Data in Page Modules

When using Page Modules, the fields of the module are passed down to the Vue component as props. However, you may run into a scenario where you want to fetch additional data into your Page Modules, such as information from a Content List or Item in Agility.

Fetching the Data

In the ./src/data directory, create a file and use the $agilitycms global object to use the Agility CMS fetch methods:

export default async ({ $agility }) => {
  // set up posts array
  let posts = [];

  // set language code
  const languageCode = $agility.languages[0];

  try {
    // raw posts
    const rawPosts = await $agility.client.getContentList({
      referenceName: "posts",
      languageCode,
    });

    // categories
    const categories = await $agility.client.getContentList({
      referenceName: "categories",
      languageCode,
    });

    posts = rawPosts.map((post) => {
      // get category id
      const categoryID = post.fields.category?.contentid;

      // find matching category
      post.linkedCategory = categories?.find((c) => c.contentID == categoryID);

      // format date
      post.formattedDate = new Date(post.fields.date).toLocaleDateString();

      // return post
      return post;
    });
  } catch (error) {
    if (console) console.error("Could not load posts list.", error);
  }

  return posts;
};

PostListing.js

Making the Data Accessible

Once you've fetched your data, import it into the agility.components.js file and add it to the dataFetch object:

// Our Agility Data Fetch
import PostsListingData from "./data/PostsListing";

export default {
  dataFetch: {
    PostsListing: PostsListingData,
  },
  moduleComponents: {
    ...
  },
  pageTemplateComponents: {
    ...
  },
};

agility.components.js

Using the Data in your Page Module

To use the Data in your Page Module, return the moduleData in the Page Module computed object:

<template>
  <ul v-for="post in posts">
   <li>{{ post.fields.title }}<li>
  <ul>
<template>
<script>
export default {
  props: {
    contentID: Number,
    item: Object,
    page: Object,
    pageInSitemap: Object,
    dynamicPageItem: Object,
    moduleData: Object,
  },
  computed: {
    posts: function() {
      // our module data was loaded in src/data/PostsLists.js
      return this.moduleData["PostsListing"];
    },
  },
};
</script>

PostsListing.vue

Page Templates

Page Templates are how developers can differentiate the styles of certain types of pages, and define where on the page that editors can add Page Modules (functional components of the page that editors control).

Some sites may only have a single page template and this is re-used across the site, others may have other templates to allow for more flexible layouts.

What is in a Page Template?

When editors create pages in Agility CMS, they must select which template they'd like to use.

A Page Template consists of a Name and Content Zones.

The Name should represent what the editor can expect from using the Page Template. For example, a site may have templates named One Column TemplateTwo Column Template, or Blog Template.

Content Zone is an area defined in the Page Template where an editor can add, edit, or remove modules. A Page Template can have one or many Content Zones.

Example

In the agilitycms-nuxtjs-starter site, the Name of the Page Template is used to find a corresponding Vue component that matches the same name. If a match is found, that component is dynamically imported and rendered.

For example, if a Page Template has a reference name of MainTemplate in the CMS, then while the page is being rendered it will look for ./src/components/agility-pageTemplates/MainTemplate.vue in the ./src/components/agility-pageTemplates directory.

Reviewing the example AgilityPage.vue from our agilitycms-nuxtjs-starter site, the componentToRender function will automatically take care of resolving and rendering the appropriate page template.

Page templates with Nuxt blog on agilitycms.com

AgilityPage.vue:

<script>
import AgilityComponents from "./agility.components";...
export default {
  ...
  computed: {
    componentToRender: function() {
      const component =
        AgilityComponents.pageTemplateComponents[this.page.templateName];
      return component;
    },
  },
  ...
}
<script>

MainTemplate.vue:

<template>
  <ContentZone
    name="MainContentZone"
    :page="page"
    :pageInSitemap="pageInSitemap"
    :dynamicPageItem="dynamicPageItem"
    :moduleData="moduleData"
  />
</template>

<script>
import ContentZone from "../../AgilityContentZone";
export default {
  props: {
    page: Object,
    moduleData: Object,
    pageInSitemap: Object,
    dynamicPageItem: Object,
  },
  components: {
    ContentZone,
  },
};
</script>

How to Add a New Page Template

When you create a new Page Template within Agility CMS, you'll want to create the corresponding Vue component for it within the .src/components/agility-pageTemplates directory.

All of the Page Templates that are being used within the site need to be imported into the agility.components.js file within the .src directory and added to the pageTemplateComponents object:

// Our Agility PageTemplates
import MainTemplate from "./components/agility-pageTemplates/MainTemplate";

export default {
  dataFetch: {
   ...
  },
  moduleComponents: {
    ...
  },
  pageTemplateComponents: {
    "Main Template": MainTemplate,
  },
};

If there is no corresponding Vue component for your Page Template, then nothing can be rendered for it on your Nuxt.js site.

How to Add a Content Zone

You can alter your content zones at any time, you'll simply have to utilize the <AgilityContentZone /> component within your Page Template Vue component. This tells the Nuxt build where to render the modules for this content zone in your code.

For example, this means that all modules in MainContentZone for a page are to be rendered here.

<ContentZone
    name="MainContentZone"
    :page="page"
    :pageInSitemap="pageInSitemap"
    :dynamicPageItem="dynamicPageItem"
    :moduleData="moduleData"
/>