DEV Community

Cover image for How React Routing works using a Headless CMS
Joel Varty for Agility CMS

Posted on • Originally published at help.agilitycms.com

How React Routing works using a Headless CMS

Agility CMS is unique in that it offers a REST API for Page Routing.  It doesn't actually do any of the routing for you - it just provides you with a routing table object that represents the sitemap, and Page objects that represent each page.

This enables you, as a developer, to offload the setup of complex sitemaps to the content team. This can be a huge time-saver and makes developing websites using a CMS much faster.

In the guide Why Agility CMS has Page Management Capabilities I outline how pages are managed in the CMS using Page Templates and Modules. 

Let's look at how that actually works in our React site.

Start with the code

First, get started with the Agility CMS React app.  It's as easy as cloning a GitHub repo and running a command line to get started.  Oh, you should also sign-up for a free Agility CMS account.

1: Clone the repo:

git clone https://github.com/agility/agility-create-react-app
Enter fullscreen mode Exit fullscreen mode

2: Install npm dependencies:

npm install
Enter fullscreen mode Exit fullscreen mode

3: Start the site locally:

npm start
Enter fullscreen mode Exit fullscreen mode

How does the routing work?

It all starts with a sitemap object, which Agility CMS gives us via the REST API.  

It looks something like this (I've simplified it a bit for clarity):

{
    "/home": {
        "title": "Home",
        "name": "home",
        "pageID": 2
    },
    "/posts": {
        "title": "Posts",
        "name": "posts",
        "pageID": 3
    }
}
Enter fullscreen mode Exit fullscreen mode

Each route in the site is accessible from this object, so you can quickly lookup whether a route exists or not.  

Here's how we use this in React.

async routePage() {
    const api = this.props.agility.client;
    try {
        //get the sitemap route table
        const sitemap = await this.getSitemap(api);

        //get the path from the browser
        const path = document.location.pathname.toLowerCase();
        const pageInSitemap = sitemap[path];

        //if we are on the homepage, get the first route
        if (path === '/') {
            const firstPagePathInSitemap = Object.keys(sitemap)[0];
            pageInSitemap = sitemap[firstPagePathInSitemap];
        }

        //only proceed if this path is in the table
        if (pageInSitemap) {

            //get the actual page object
            const page = await api.getPage({
                pageID: pageInSitemap.pageID,
                languageCode: this.props.agility.config.languageCode
            });

            //set this page in our state object
            this.setPage(page, pageInSitemap);

        } else {
            //Could not find page
            this.pageNotFound();
        }

    } catch (error) {
        //Throw error
        this.handleError('error getting sitemap :(', error);
    }
}
Enter fullscreen mode Exit fullscreen mode

We first load the sitemap, then we use the current location.pathname to check if the current page is an available route.  If our current route is "/", then we use the first page in the sitemap.

Now, we take that route object, called pageInSitemap, and call getPage() with the pageID.

Once we have the page object, Aglity CMS gives us all the data we'll need to actually render this page.

In our file called agility.config.js, we setup which React Components will render for each Page Template and Module Definition that is configured on any available page object (this is all setup in the CMS earlier).

//Our Agility Modules
import RichTextArea from './modules/RichTextArea'
import Jumbotron from './modules/Jumbotron'

//Our Agility PageTemplates
import OneColumnTemplate from './pageTemplates/OneColumnTemplate'

export default {
    guid: '...', //Set your guid here
    fetchAPIKey: '...', //Set your fetch apikey here
    previewAPIKey: '...', //set your preview apikey
    languageCode: 'en-us',
    channelName: 'website',
    isPreview: true,
    moduleComponents: {
        RichTextArea,
        Jumbotron
    },
    pageTemplateComponents: {
        OneColumnTemplate
    }
}
Enter fullscreen mode Exit fullscreen mode

We have 2 page templates setup, as well as 4 different modules.  Each will be rendered by a different component, which we specify with the import statements at the top, and each component will be delivered a props variable that has all the data it needs from Agility CMS.

Page Template Components

Let's take a look at the OneColumnTemplate component that renders our Page Template.

import React, { Component } from 'react';
import { ContentZone } from '../agility-react'


class OneColumnTemplate extends Component {
    render() {    
        return (
        <div className="one-column-template">
            <ContentZone name='MainContentZone' {...this.props} />
        </div>
        );
    }
}

export default OneColumnTemplate;
Enter fullscreen mode Exit fullscreen mode

It's pretty simple - all we are doing here is dropping a component with a name attribute that matches the zone defined in our Page Template.  We also pass through the props - we'll see how that's important when we render our Module components.  The ContentZone component will now look at the Page object to render out the modules that have been dropped onto it by our content editors.

Module Components

Each Module in Agility CMS can have it's own properties.  Those get passed into our components as props.  Here's our JumboTron component, which is a simple example of rendering a heading and a sub-heading:

import React, { Component } from 'react';

import './Jumbotron.css'

class Jumbotron extends Component {
    render() {    
        return (
            <section className="jumbotron">
                <h1>{this.props.item.fields.title}</h1>
                <h2>{this.props.item.fields.subTitle}</h2>
            </section>
        );
    }
}

export default Jumbotron;
Enter fullscreen mode Exit fullscreen mode

In this case, it's a simple matter of outputting the title and subTitle properties into header tags. These   have been setup in Agility CMS as Module Properties. As you add more properties to your modules with different content types, they become available to you as props.

One more thing...

There is a really important piece that I haven't spoken about yet: the react-router component, which allows us to only change a main section of each page when the route changes.

Take a look at our App.js file:

import React, { Component } from 'react';
import { Switch, Route } from 'react-router-dom'

import './App.css';

//The Agility Router
import { PageRouter } from './agility-react'

//Shared Components
import GlobalHeader from './GlobalHeader'

class App extends Component {
  componentDidMount() {

  }

  render() {
    return (
      <div className="App">
        <GlobalHeader agility={this.props.agility} />
        <main className="main">
          <Switch>
            <Route path="*" render={() => <PageRouter agility={this.props.agility} />} />
          </Switch>
        </main>
      </div>
    );
  }
}

export default App;
Enter fullscreen mode Exit fullscreen mode

The and components are controlled by the react-router, and we have a  component that triggers all the logic that I described above. 

That's a really simple look at how we perform routing in a React app with Agility CMS Pages.

I think it's kind of magical when you get all this in place and allow your content editors to create all kinds of pages and put modules on them wherever they please. It takes your work as a developer and multiplies it.  

That's one of the ways that Agility CMS is the fastest CMS!

Discussion (0)