Web Studio is here! An enhanced experience to make it easier to create, preview, and collaborate on your website contentLearn More
The 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 Starter instance. Don't have one? Sign up for one - https://agilitycms.com/free
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
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 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.
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.
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.
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>
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.
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.
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.
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;
};
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: {
...
},
};
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>
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.
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 Template, Two Column Template, or Blog Template.
A 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.
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.
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>
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.
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"
/>