Web Studio is here! An enhanced experience to make it easier to create, preview, and collaborate on your website contentLearn More
The Next.js Blog Starter showcases features such as our native Page Management and shows how you should structure your Next.js website. This starter serves an example based on our recommended best-practices.
When using Agility CMS, we believe Editors should have full control over the pages on their website and should not need to rely on a developer. The agilitycms-next-starter makes it easy to source content and also generates the pages of your website based on your sitemap in Agility CMS.
Editors in the CMS can control what pages are available, what the URLs are, what page models they're using, and exactly what UI Components (Modules) make up each and every page.
The above figure represents a sitemap of Pages as well as the Components that are on each - all are managed in the CMS.
This means that when a build occurs in your Next.js 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 during build time, the final step is rendering the page to HTML. In the agilitycms-nextjs-starter site, the [...slug].js
file located within the pages directory takes care of this for you. It returns a layout.js component that passes all necessary props to render each page.
Source code for /pages/[...slug].js:
import Layout from "components/common/Layout";
import { getAgilityPageProps, getAgilityPaths } from "@agility/nextjs/node";
import { getModule } from "components/agility-pageModules";
import SiteHeader from "components/common/SiteHeader";
// getStaticProps function fetches data for all of your Agility Pages and Next.js will pre-render these pages at build time
export async function getStaticProps({
preview,
params,
locale,
defaultLocale,
locales,
}) {
// place all global here
const globalComponents = {
header: SiteHeader,
};
const agilityProps = await getAgilityPageProps({
preview,
params,
locale,
getModule,
defaultLocale,
globalComponents,
});
if (!agilityProps) {
// We throw to make sure this fails at build time as this is never expected to happen
throw new Error(`Page not found`);
}
return {
// return all props
props: agilityProps,
// Next.js will attempt to re-generate the page when a request comes in, at most once every 10 seconds
// Read more on Incremental Static Regenertion here: https://nextjs.org/docs/basic-features/data-fetching#incremental-static-regeneration
revalidate: 10,
};
}
// Next.js will statically pre-render all the paths from Agility CMS
export async function getStaticPaths({ locales, defaultLocale }) {
//get the paths configured in agility
let agilityPaths = await getAgilityPaths({
preview: false,
locales,
defaultLocale,
});
return {
paths: agilityPaths,
fallback: true,
};
}
const AgilityPage = (props) => {
return <Layout {...props} />;
};
export default AgilityPage;
getStaticProps
is a function in Next.js that will pre-render a page at build time using the props that it returns. getAgilityPageProps
will get all the context for a particular page and return it as your props.
export async function getStaticProps({
preview,
params,
locale,
defaultLocale,
locales,
}) {
const globalComponents = {
header: SiteHeader,
};
const agilityProps = await getAgilityPageProps({
preview,
params,
locale,
getModule,
defaultLocale,
globalComponents,
});
if (!agilityProps) {
throw new Error(`Page not found`);
}
return {
props: agilityProps,
revalidate: 10,
};
}
Take note of the revalidate tag. When your Next.js and Agility CMS site is hosted on Vercel, the revalidate tag turns your deployment into an instant build machine - meaning your pages are built as they change!
Content Items stored in Agility that you want to appear or have access to globally across your site such as a Site Header or Site Footer Component should be imported and placed within the globalComponents
object.
const globalComponents = {
header: SiteHeader,
};
The props
are then passed into the AgilityPage
Component which returns a Page Component that will handle your Page Models.
const AgilityPage = (props) => {
return <Layout {...props} />;
};
The Layout.js
Component resolves which PageModel should be rendered (<AgilityPageTemplate />
) and passes the necessary content as props
. The Layout.js component will also handle your site SEO and Preview.
// set up handle preview
const isPreview = handlePreview();
function Layout(props) {
const {
page,
sitemapNode,
dynamicPageItem,
notFound,
pageTemplateName,
} = props;
const router = useRouter();
if (router.isFallback) {
return ;
}
if (notFound === true) {
return ;
}
const AgilityPageTemplate = getPageTemplate(pageTemplateName);
if (dynamicPageItem?.seo?.metaDescription) {
page.seo.metaDescription = dynamicPageItem.seo.metaDescription;
}
return (
<>
<SEO
title={sitemapNode?.title}
description={page.seo.metaDescription}
keywords={page.seo.metaKeywords}
metaHTML={page.seo.metaHTML}
/>
<div id="site-wrapper">
{isPreview && <LoadingWidget message="Loading Preview Mode" />}
{!isPreview && (
<div id="site">
<PreviewBar {...props} />
<div className="flex flex-col min-h-screen">
<SiteHeader {...props} />
<main className="flex-grow">
<AgilityPageTemplate {...props} />
</main>
<SiteFooter {...props} />
</div>
</div>
)}
</div>
</>
);
}
export default Layout;
Page Models in Agility CMS use the <ContentZone />
Component from the @agility/nextjs
package and the getModule
function from the agility-pageModules
directory to identify where Components for specific Zones should be rendered within the Page Model. Here's an example for a "Main Page Model":
import React from "react";
import { ContentZone } from "@agility/nextjs";
import { getModule } from "components/agility-pageModules";
const MainTemplate = (props) => {
return (
<ContentZone name="MainContentZone" {...props} getModule={getModule} />
);
};
export default MainTemplate;
Within each
Component defined in the template, each Page Model that exists on the Content Zone in the CMS is resolved to a React Component and rendered in-place.<ContentZone />
In Agility CMS, Component Models are functional components Editors use 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 consist of. Each Component defined within Agility CMS should have a corresponding React Component in your Next.js site.
If components are not defined, or the editor do not add components to pages, then there won't be anything to output on your pages.
In our agilitycms-nextjs-starter site, the name of the component is used to find a corresponding react component that matches the same name. If there is a match, then that component will be dynamically imported and rendered for you.
For example, if a component has the name of RichTextArea in the CMS, while Next.js is rendering your pages, it will look for RichTextArea.js
inside of agility-pageModules
directory.
The <ContentZone />
component on your Page Model will dynamically import the module and pass all the field values for the component as props
.
RichTextArea.js:
import React from "react";
import { renderHTML } from "@agility/nextjs";
const RichTextArea = ({ module }) => {
// get module fields
const { fields } = module;
return (
<div className="relative px-8">
<div className="max-w-2xl mx-auto my-12 md:mt-18 lg:mt-20">
<div
className="prose max-w-full mx-auto"
dangerouslySetInnerHTML={renderHTML(fields.textblob)}
/>
</div>
</div>
);
};
export default RichTextArea;
If you create a new Component Model within Agility CMS, you'll want to create the corresponding React component for it within the agility-pageModules
directory.
All of the Component Models that are being used within the site need to be imported into the index
file within the agility-pageModules
directory and added allModules
array:
import RichTextArea from "./RichTextArea";
import FeaturedPost from "./FeaturedPost";
import PostsListing from "./PostsListing";
import PostDetails from "./PostDetails";
import Heading from "./Heading";
import TextBlockWithImage from "./TextBlockWithImage";
const allModules = [
{ name: "TextBlockWithImage", module: TextBlockWithImage },
{ name: "Heading", module: Heading },
{ name: "FeaturedPost", module: FeaturedPost },
{ name: "PostsListing", module: PostsListing },
{ name: "PostDetails", module: PostDetails },
{ name: "RichTextArea", module: RichTextArea },
];
export const getModule = (moduleName) => {
if (!moduleName) return null;
const obj = allModules.find(
(m) => m.name.toLowerCase() === moduleName.toLowerCase()
);
if (!obj) return null;
return obj.module;
};
If there is no React component for your Component Model, then nothing can be rendered for it in your Next.js 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.
Page Models in Agility CMS allow developers to differentiate the styles of certain types of pages, and also define where on the page editors can add and edit Component Models (functional components of the page that editors can control).
Depending on your scenario, some sites may only use a single page model and re-use it across the site, while other sites may have multiple page models to allow for more flexible pages.
When editors create pages in Agility CMS, they must select which Page model they'd like to use.
A Page Model consists of a Name and Zones.
The Name should represent what the editor can expect from using the Page Model. For example, a site may have Page Models named One Column Template, Two Column Template, or Blog Template.
A Zone is an area defined in the Page Model where an editor can add, edit, or remove components. A Page Model can have one or many Zones.
In the agilitycms-nextjs-starter site, the Name of the Page Model is used to find the corresponding React Component that matches the same name. If a match is found, that Component will be dynamically imported and rendered for you.
For example, if a Page Model has the name of Main Template in the CMS, while the page is being rendered, it will look for MainTemplate.js
inside of the agility-pageTemplates
directory.
Taking a look at the Layout.js
file from our agilitycms-nextjs-starter site, the <AgilityPageTemplate />
Component will automatically take care of resolving and rendering the appropriate Page Model
Layout.js:
const AgilityPageTemplate = getPageTemplate(pageTemplateName);
mainTemplate.js:
import React from "react";
import { ContentZone } from "@agility/nextjs";
import { getModule } from "components/agility-pageModules";
const MainTemplate = (props) => {
return (
<ContentZone name="MainContentZone" {...props} getModule={getModule} />
);
};
export default MainTemplate;
When you create a new Page Model within Agility CMS, you'll want to create the corresponding React Component for it within the agility-pageTemplates
directory.
All of the Page Models that are being used within the site need to be imported into the index file within the agility-pageTemplates
directory and added allTemplates
array:
import MainTemplate from "./MainTemplate";
const allTemplates = [
{ name: "MainTemplate", template: MainTemplate }
];
export const getPageTemplate = (templateName) => {
if (!templateName) return null;
const obj = allTemplates.find(
(m) => m.name.toLowerCase() === templateName.toLowerCase()
);
if (!obj) return null;
return obj?.template;
};
If there is no corresponding React Component for your Page Model, then nothing can be rendered for it in your Next.js site.
You can alter your Zones at any time, simply import
from the ContentZone
package, then utilize @agility/nextjs
<ContentZone />
within your Page Model React Component. The <ContentZone />
Component will tell your Next.js site where to render the modules for this Zone within your code.
For example, this means that all Component Models in MainContentZone
for a page are to be rendered here.
<ContentZone name="MainContentZone" {...props} getModule={getModule} />
Where possible, we always recommend storing your images in Agility CMS's Assets area. These are automatically deployed to a CDN and support basic image transcoding via query-strings. You can simply continue to use the CDN URLs provided for your content without any additional work.
However, one of the primary benefits of having a static website is that nothing on your site can change without a new build. This ensures your website updates are atomic. If you are using CDN links to images though, in your site, one could alter an image and that change would take effect immediately on your site. You'd also have no way of reliably rolling back your site to a previous version since the images on the CDN are managed separately.
Next.js has a built-in Image Component and Automatic Image Optimization. You can take advantage of this Image Optimization via the <Image />
Component exported by next/image
.
It's important to note that this component will optimize images using the web server's CPU, and any Agility images served through this component will not be automatically invalidated when they are changed in Agility.
For Agility images, we recommend using the <AgilityImage> component, described below.
AgilityImage
is a wrapper for the next/image
Component that comes configured to change image URL parameters to resize, optimize format as well as adjust image quality on our CDN.
We recommend using this component for your Agility images in Next.js sites.
See here for all available props.
import { AgilityImage } from "@agility/nextjs"
<AgilityImage
src={fields.image.url}
alt={fields.image.label}
width="768"
height="512"
className="rounded-lg object-cover object-center"
/>
Being able to preview content prior to publishing is a feature every editor should have. With Vercel, previewing your Agility CMS changes on your Next.js site is extremely easy.
When your Agility CMS and Next.js site is configured for Previews, clicking the Preview button on your pages will open up a new tab with your Preview Environment.
A cookie is set to let Vercel know that you are opening this page in Preview Mode. You will notice a handy Preview Bar at the top of your page with a toggle that allows you to switch between Preview and Live Mode to make it easy for you to view your Published Content or Latest Changes.
Further Reading