Robbert Kauffman

Mar 9, 2018

Integrate a React application with BloomReach Experience

 

This tutorial explains how to integrate your React application with BloomReach Experience. If you’re using Angular instead then have a look at the example Angular App. An Angular tutorial will soon follow. For any of the other popular frameworks such as Vue.js please contact us for details on how to integrate your front-end application with BloomReach Experience.

Before continuing, we recommend to first read the high-level architecture of SPA integrations as explained in A new approach to integrating SPAs with WCM: fixing what’s wrong with Content-as-a-Service. This will provide the necessary context on the goal architecture we are aiming to replicate in this tutorial.

High-level overview

Following the steps specified in the architecture overview, the integration covers the following:

  1. Enable Page Model API in BloomReach Experience

  2. Render page(-sections) in React using Page Model API response

  3. Have React power Preview Mode in CMS

    1. Include CMS meta-data with HTML

    2. Defer state updates to React and render CMS authoring overlays


We’ll go over these in detail step-by-step.

1. Enable Page Model API & Channel Manager Integration in BloomReach Experience

As of version 12.3.0 the Page Model API is part of the core product. So no dependencies have to be installed, only some configuration needs to be added. To enable the Page Model API, follow the instructions in Configure SPA++. Alternatively, you can use the SPA++ Integration Demo project which has the Page Model API already enabled.

Please note that for projects created using the archetype prior to v12.4.0 you will have to enable the Channel Manager Integration manually by adding a dependency. This is needed for enabling the authoring overlays in the Channel Manager.

The Channel Manager Integration requires BloomReach Experience and is not supported for Hippo CMS Community Edition. If you would like to get BloomReach Experience, please contact us or read more on Get BloomReach Experience Developer Accounts.

Once you have followed the instructions, you should be able to access the API locally at http://localhost:8080/site/resourceapi or whatever path you have specified in the host configuration. Before continuing, check the URL of the API and if you can get a proper response from the API. The output should roughly look as followed:

{
  id: "…",
  page:
  {
    components: 
    [
      … // array of root-level components on the page
      {
        components:
        [
          … // array of nested components
        ]
      }
    ]
  },
  content:
  {
    … // map of documents that are referenced by components on the page
  }
}

There is also Swagger documentation available for the Page Model API.

If you are not using the SPA++ Integration Demo project, make sure that the homepage (or whichever page you’re outputting) contains at least one container with at least one component nested in the container.

 

2. Render page(-sections) in React using Page Model API response

The next step is to let React render page(-sections) using the Page Model API response. Fortunately, there is an example React application that also serves as a library to keep the development effort of the integration minimal. Please note that this is a reference implementation, so you’re completely free to rewrite it or code it entirely yourself.

 

2.1 Add the React library

The React library is not available as a Node package yet. This might be done in the near future. For now, copy over all of the library code (the src folder) to your React app. The benefit of doing this is that you can easily modify any of the code as you see fit and you don’t have to rely on an API that we provide. For the examples of this tutorial, we’re assuming that the contents of the src folder of the library have been copied to a folder named bre-integration that is located in the sources folder of the React application (this is typically the src folder).

The library requires three dependencies. Add these using the command below. Alternatively, you can reference the package.json.

yarn add html-react-parser jsonpointer react-router-dom

 

Warning: Please note that the library and the examples used in this tutorial are written for React 16.2.x. In case you use an older version of React, please keep in mind that some of the code might not be supported in your version.

Go to the class of your React app that you would like BloomReach Experience to power. For example, the class that is responsible for rendering the homepage. In this class, start by adding the following import statement:

import CmsPage from './bre-integration/cms-components/core/page';

Make sure to update the path to the dependency accordingly, in case the path to the dependency is different in your React app. And remember to do this for any of the other import statements in this tutorial as well.

Next, add the following line in the render() method of the class at the exact location where you want BloomReach Experience to power the HTML:

<CmsPage pathInfo={""} preview={""} />

As you can see from the above code, the CmsPage component requires you to pass the following properties:

  • pathInfo: this is the path information that usually comes after the domain-name in a URL and indicates the page that is being requested. Passing this information ensures that the Page Model API returns the response for the corresponding page in BloomReach.

  • preview: when viewing the site in Preview Mode in the CMS, the site URL is prefixed with /cmsinternal. Similarly, the Page Model API should also be prefixed when Preview Mode is active so that the response includes preview content and CMS meta-data. This property acts as a toggle for when the site is viewed in Preview Mode in the CMS.

All of these properties can be inferred from the URL. An example of how this can be done can be found in the index.js of the example React application, which is also shown below.

export default class HomePage extends React.Component {
  render() {
    const pathInfo = this.props.match.params.pathInfo;
    const preview = this.props.match.params.preview;

    return (
      <CmsPage pathInfo={pathInfo} preview={preview} />
    );
  }
}

let routePath = '/:pathInfo*';
if (baseUrls.cmsChannelPath !== '') {
  routePath = `/(${baseUrls.cmsChannelPath})?` + routePath;
}
routePath = `/:preview(${baseUrls.cmsPreviewPrefix})?` + routePath;
if (baseUrls.cmsContextPath !== '') {
  routePath = `/(${baseUrls.cmsContextPath})?` + routePath;
}

ReactDOM.render(
  <BrowserRouter>
    <Switch>
      <Route path={routePath} component={HomePage} />
      <Redirect to="/" />
    </Switch>
  </BrowserRouter>,
  document.getElementById('root')
);

The example uses React Router and regexp matchers to detect URL patterns such as the preview URL (/_cmsinternal), and passes these as properties to the component. You will have to do something similar in your React app. How exactly depends entirely on your React app, what library you use for routing, if you use a server-side rendering framework (e.g. next.js), etc. Please refer to the documentation of the special libraries and/or frameworks you’re using, on how to access URL information within the app.

Once you pass the URL information as properties to the CmsPage component, it should render something on the site.

The React app should now render some content depending on the containers and components that are on the homepage in BloomReach Experience. If you’re using the demo project, it should render two banners. If you do see something extra rendered on the page, you can continue to the next section, otherwise see the quick troubleshooters below.

Troubleshooting: CORS

Not seeing anything extra being rendered? Check the browser’s console of the developer tools for the following message: “Error while fetching CMS page data for URL: ...”. If you’re also seeing error messages about Access-Control-Allow-Origin headers and CORS, then you need to enable CORS for your project: follow the instructions as described in the section Configure CORS Headers in Configure SPA++.

Troubleshooting: You should not use <Link> outside a <Router>

The library uses the <Link> tag to create internal links. This only works if React Router is used in the app, as in the above code example. If your app doesn't use React Router, then you will have to comment out the <Link> tags so that all links are rendered as regular links with anchor tags. You can do this by commenting out or removing lines 42-44 and 46 in cms-components/core/menu.js:

renderLink (configuration, active) {
  if (configuration._links && configuration._links.site && configuration._links.site.href) {
    // if (configuration._links.site.type === 'internal') {
    //   return (<Link className="nav-link" to={configuration._links.site.href}>{configuration.name}{active}</Link>);
    // } else {
      return (<a className="nav-link" href={configuration._links.site.href}>{configuration.name}{active}</a>)
    // }
  }
  return null;
}

 

Troubleshooting: Compilation error - Check the render method of `CmsContainer` or `ContentComponent`

Getting the following compilation error?: "Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in."

Along with: "Check the render method of `CmsContainer` or Check the render method of `ContentComponent`"

This is caused by using an older version of React that doesn’t support the <React.Fragment> tag (supported since ^16.2.x). Either update your React version to the latest version or do a search & replace for all <React.Fragment> and </React.Fragment> tags in the source files in the bre-integration folder, and replace them with <div> and </divs> respectively.

2.2 Define custom components

The React app now renders banners, and can also show a news list, or a news detail page. These are the components that are currently defined in the library and in the CMS. Of course you don’t want to be constrained to just these components and you will want to define your own components so that you can manage these components from the CMS: drag-and-drop the components to a page, edit the components, reorder components, etc.

In most cases, you already have a bunch of components defined in your React app. These components can be modified so that these can be managed from the CMS.

There are two wrapper classes for components which carry out most of the logic needed to make your components CMS-managed. This ensures that your component classes stay nice and clean, and need as little BloomReach-specific code as possible. The wrapping classes are:

  • CmsContainerItem: component wrapping class which adds the CMS meta-data via HTML comments so that the component is recognized in Preview Mode in the CMS.

  • ContentComponentWrapper: additional wrapping class on top of CmsContainerItem for adding CMS meta-data via a HTML comment to enable in-context editing (orange edit content button). Also looks up the corresponding content in the content map and passes this as the property content to the component class it is wrapped around.

Because of these two wrapping classes, it’s helpful to distinguish two types of components: standard components that don’t need in-context editing of its content, like a news list, product grid, etc.; and content components that show a single content-item and thus in-context editing can be enabled for it.

Regardless of the types of component, all CMS-managed React components are defined in the bre-integration/component-definitions.js file. The definitions of the example React application look as follows:

import Banner from './cms-components/essentials/banner';
import Content from './cms-components/essentials/content';
import NewsList from './cms-components/essentials/news-list';
import NewsItem from './cms-components/essentials/news-item';

const componentDefinitions = {
  "Banner": { component: Banner, contentComponent: true },
  "Content": { component: Content, contentComponent: true },
  "News List": { component: NewsList },
  "News Item": { component: NewsItem, contentComponent: true }
}

export { componentDefinitions };

 

It’s essentially a map containing all of the component definitions. The key needs to match the hst:label of the corresponding component in BloomReach Experience.

Each key contains a map where you define the React component to use for rendering the component. If the component needs in-context editing, you will also have to add the property contentComponent: true to the same map.

2.2.1 Default properties passed to React components

For simple components that don’t reference any CMS content and don’t need additional controls in the CMS, all you have to do is add the component definitions as described in the previous step. After this, the component can be dragged-and-dropped in the CMS for placement on pages.

However, in most cases, you will want to be able to control the component from the CMS using (custom) component dialogs. The component settings managed through the dialog are passed as properties to the React component. The CmsContainerItem wrapping class does all of the plumbing for this.

Three different properties are passed:

  • configuration: map containing the properties of the component. See more details below.

  • pageModel: the response of the page Model API, which is used to lookup any references to content by the component.

  • preview: toggle for determining if Preview Mode is active in the CMS. This is typically a boolean but it can also have other values depending on how this property is populated in your React app. See section 2.1.

2.2.1.1 Component configuration and component logic

The component configuration map has the following structure:

id             // component ID, unique ID
name           // component name, cannot be used to determine component-type as name changes when you add multiple components of same type, e.g. banner, banner2, banner3
type           // component super-type, e.g. container, container-item
label          // unique name used in the CMS component catalog, which can be used to map CMS components to React components
models: {}     // variables set by the component in the doBeforeRender phase, e.g. result of a query
_meta: {
  paramsInfo: ()     // resolved parameters of the component's settings
  params: {}         // raw parameters of the component's settings
  beginNodeSpan: {}  // contains the CMS meta-data for enabling drag-and-drop in Preview Mode, only used internally by the library
  endNodeSpan: {}    // contains the CMS meta-data for enabling drag-and-drop in Preview Mode, only used internally by the library

 

Models and _meta are the most important data of the component configuration. The _meta/paramsInfo are useful for simple settings, like toggles and styling -- parameters that don’t need any processing. For parameters that do need processing, like referenced content, search queries, -- the CMS component class does the processing of these parameters and stores the results in the models map. For example, find content via the stored references, do search queries. The results are stored as references in the models map as JSON pointer. The actual content is included in the content map of the Page Model API response. This prevents content from being duplicated in the response when it is referenced by multiple components.

Example configuration for a news list:

id: "r5_r1_r1",
name: "newslist",
componentClass: "org.onehippo.cms7.essentials.components.EssentialsNewsComponent",
type: "CONTAINER_ITEM_COMPONENT",
label: "News List",
models: {
  pageable: {
    pageSize: 10,
    visiblePages: 10,
    total: 3,
    showPagination: true,
    items: [
      {
        $ref: "/content/uaeda2bcdb21d4eada2e6c64a2ca051c8"
      },
      {
        $ref: "/content/u30092f4e2ef74c7286a58ce895908937"
      },
      {
        $ref: "/content/uc580ac6438744717a6d9e5ad72080abe"
      }
    ],
    maxSize: 11,
    currentPage: 1,
    next: false,
    previousPage: null,
    previous: false,
    previousBatch: false,
    nextBatch: false,
    pageNumbersArray: [1],
    endPage: 1,
    startPage: 1,
    totalPages: 1,
    currentRange: [1],
    nextPage: null,
    startOffset: 0,
    endOffset: 3
  }
},
_meta: {
  paramsInfo: {
    path: "",
    pageSize: 10,
    sortOrder: "desc",
    documentTypes: "hippodemospaintegration:newsdocument",
    sortField: "",
    includeSubtypes: false,
    showPagination: true,
    hideFutureItems: true,
    documentDateField: "hippodemospaintegration:date"
  },
  params: {
    hidePastEvents: "on",
    documentTypes: "hippodemospaintegration:newsdocument",
    sortField: "",
    sortOrder: "desc",
    includeSubtypes: "off",
    showPagination: "on",
    path: "",
    pageSize: "10",
    documentDateField: "hippodemospaintegration:date",
    hideFutureItems: "on",
    bloomreach: "webfile:/freemarker/hippodemospaintegration/base-layout.ftl",
    angular: "webfile:/freemarker/hippodemospaintegration/angular-base-layout.ftl",
    react: "webfile:/freemarker/hippodemospaintegration/react-base-layout.ftl"
  },
  beginNodeSpan: [
    {
      type: "comment",
      data: "<!-- { "HST-Label":"News List", "HST-LastModified":"1529947577580", "HST-XType":"hst.item", "uuid":"607c7f2d-4df8-4383-8420-4f7c9f455cdb", "HST-Type":"CONTAINER_ITEM_COMPONENT", "refNS":"r5_r1_r1", "url":"/site/_cmsinternal/resourceapi/news?_hn:type=component-rendering&_hn:ref=r5_r1_r1", "HST-Render-Variant":""} -->"
    }
  ],
  endNodeSpan: [
    {
      type: "comment",
      data: "<!-- { "uuid":"607c7f2d-4df8-4383-8420-4f7c9f455cdb", "HST-End":"true"} -->"
    }
  ]
},
_links: {
  componentRendering: {
    href: "/site/_cmsinternal/resourceapi/news?_hn:type=component-rendering&_hn:ref=r5_r1_r1"
  }
}

 

In this example, the _meta/paramsInfo map contains a toggle for showing pagination, showPagination. This can be used in your React component to toggle the display of pagination, e.g. previous and next buttons. A bunch of other parameters are passed as well, but these are only used by the CMS to control what news content should appear in the list. The CMS component uses these parameters to query for the right content and consequently, these parameters should not need to be used in your React component. The result of the query is stored in the models map and, as mentioned earlier, in the form of references to the UUIDs of the content. The actual content can be looked up in the content map using these UUIDs.

What if I don’t want to put all the component logic in the CMS?

Instead of placing most of the logic in the CMS components, you can also move this logic to the React components. Although this is partly a design decision, generally it will make the most sense to keep all logic related to BloomReach content in the CMS components. There are lots of Java APIs in the delivery tier for easily querying content that you can leverage, saving you from having to recreate similar logic in your React app. For logic around data that is not stored in BloomReach (e.g. product data coming from eCommerce), it can potentially be easier to put this logic in the React app. In this case, the CMS can still control the settings of the component (e.g. search query for the product grids, additional filters) and will pass these to the React app that executes the query using the parameters.

2.2.1.2 Example: NewsList component

Let’s continue with the News list example of the previous section. The component configuration response was that of the Essentials News list Component.

Suppose we want to use the following HTML for rendering the news list:

<div className="row">
  <div className="col-sm-12 news-list">
    <!-- start news item -->
    <div className="blog-post has-edit-button">
      <h2 className="blog-post-title">
        <a href="LINK_TO_FULL_NEWS_ITEM">TITLE_OF_NEWS_ITEM</a>
      </h2>
      <p className="blog-post-meta">
        <span className="blog-post-date">DATE_OF_NEWS_ITEM</span>
        <span className="author"><a href="#pagination">AUTHOR_OF_NEWS_ITEM</a></span>
      </p>
      <p>NEWS_ITEM_INTRO</p>
    </div>
    <!-- end news item -->
  </div>
  <nav className="blog-pagination">
    <a className="btn btn-outline-primary disabled" href="#pagination">Older</a>
    <a className="btn btn-outline-secondary disabled" href="#pagination">Newer</a>
  </nav>
</div>

 

To create a React component using the component configuration response of the previous section, start with the following:

import React from 'react';
import Placeholder from '../core/placeholder';
import ContentComponentWrapper from '../core/content-component-wrapper';

export default class NewsList extends React.Component {
  render() {
    const preview = this.props.preview;
    const pageModel = this.props.pageModel;
    const configuration = this.props.configuration;

    if (configuration && configuration.models && configuration.models.pageable && configuration.models.pageable.items
      && configuration.models.pageable.items.length !== 0) {
      list = configuration.models.pageable.items;
    } else if (preview) {
      return (
        <Placeholder componentType={configuration.type} />
      );
    } else {
      // don't render placeholder outside of preview mode
      return null;
    }
  }
}

 

In the first few lines we're making the component properties easier to reference for later on by assigning them to new variables. This is done for preview, pageModel, and configuration, which are passed by the ContentComponentWrapper wrapper class.

Then we check if the component configuration contains the models map and if this contains a list that is not empty. Otherwise we render the default Placeholder component.

Continue by adding a loop that outputs news items for each of the content references in the list. Add the following within the render method:

// build list of news articles
const listItems = list.map((listItem, index) => {
  if (configuration && typeof configuration === 'object' && configuration.constructor === Object) {
    // change type as we want to render the NewsItem component
    const newsItemConfig = {label: 'News Item'};
    if ('$ref' in listItem) {
      return (
        <ContentComponentWrapper documentUuid={listItem.$ref} configuration={newsItemConfig} pageModel={pageModel}
                                 preview={preview} key={index}/>
      );
    }
  }
  console.log('NewsList component configuration is not a map, unexpected format of configuration');
  return null;
});

 

The code is explained in detail in the comments within the code. In short, we're looping over the list map and rendering the ContentComponentWrapper which wraps the NewsItem component.

Finally, add to the render() method the HTML which includes the loop of the previous step:

return (
  <div className="row">
    <div className="col-sm-12 news-list">
      {listItems}
    </div>
    <nav className="blog-pagination">
      <a className="btn btn-outline-primary disabled" href="#pagination">Older</a>
      <a className="btn btn-outline-secondary disabled" href="#pagination">Newer</a>
    </nav>
  </div>
);

 

The component will not work yet because we haven’t defined the NewsItem component yet. We’ll do that in the next section.

As a reference, you can access the full code of the NewsList component here.

2.2.2 Content components: additional properties passed

Like mentioned earlier, there is an additional wrapping class ContentComponentWrapper for content components: components that reference content and need to have in-context editing enabled (the orange edit content button).

For content components, two additional properties are passed:

  • content: contains the single content-item that is referenced by the component, as opposed to all content that is included in the content map.

  • manageContentButton: React element that controls the placement of the orange edit content button for in-context editing.

Just like the CmsContainerItem wrapper class, the ContentComponentWrapper passes the properties pageModel, and preview as well.

2.2.2.1 Example: NewsItem component

Since the NewsList component of the previous section wasn’t complete yet, let’s finish the work by implementing the NewsItem component. This way the NewsList component can render the content for each news article in the list.

We’re creating a regular React component and will add the following code to the render() method:

import React from 'react';
import { parseDate } from '../../utils/date';

export default class NewsItem extends React.Component {
  renderLink (content) {
    if (content._links && content._links.site && content._links.site.href) {
      return (<a href={content._links.site.href}>{content.title}</a>)
    }
    return null;
  }
  
  render () {
    const content = this.props.content;
    const manageContentButton = this.props.manageContentButton;    
  }
}

 

Similar to what we did in the NewsList component, we’re binding some of the properties that are passed by the ContentComponentWrapper wrapper class to variables, so that these can be more easily referenced within the component. Additionally, there is a helper method for rendering links to the detail page of the news item.

Finally, all we need to do is render the HTML and make sure we place the orange manage content button somewhere within the HTML using the manageContentButton variable. Additionally, for parsing the date of the news article, the helper function parseDate() is used.

return (
  <div className="blog-post has-edit-button">
    { manageContentButton && manageContentButton }
    <h2 className="blog-post-title">
      { this.renderLink(content) }
    </h2>
    <p className="blog-post-meta">
      { content.date &&
        <span className="blog-post-date">{parseDate(content.date)}</span>
      }
      { content.author &&
        <span className="author"><a href="#pagination">{content.author}</a></span>
      }
    </p>
    { content.introduction &&
      <p>{content.introduction}</p>
    }
  </div>
);

 

That’s all! Now the NewsList component should be manageable from the CMS, assuming you’re using the Essentials News List component in the CMS.

As a reference, you can access the full code of the NewsItem component here.

For the orange manage content buttons to be rendered correctly in Preview, you will have to add the CSS property position: relative to the parent HTML element of where the edit content button is placed.

2.2.3 Modify existing React components

We have covered a lot of details so far. Here's a a brief recap of of what you need to do when modifying existing React components, so that these can be managed from the CMS:

  1. Create CMS component:

    1. Define component parameters. Can use any of the Standard Essentials components.

    2. Create component Java class for custom components when not using the Standard Essentials components.

    3. Add component to component catalog

  2. Modify React component:

    1. Add component to component-definitions.js: import component class and add component to the componentDefinitions map.

    2. Modify React component class:

      1. Leverage _meta/paramsInfo map for simple parameters that don’t require any processing, e.g. toggles, styling controls.

      2. Use models map for data that requires processing, e.g. querying content.

      3. For components that reference a single content-item: use the content property to access the associated content-item; and use the manageContentButton property for placement of the orange edit content button that enables in-context editing.

Important: There needs to be a contract between components defined in the CMS, and components defined in the React app.

2.2.4 React component Examples

There are a few examples of React components in the example React application. This can help serve as a reference on how to create or modify React components so that they can be managed from the CMS:

  • Banner: Essentials Banner component. Is wrapped in ContentComponent for in-context editing. Uses image-url.js helper function for generating fully qualified URLs to the banner image.

  • Content: Essentials Content component that is used for showing content on detail pages such as news detail, blog detail, etc. Similar to Banner component, is wrapped in ContentComponentWrapper and uses image-url.js helper function. Additionally, uses the date.js helper function to parse and output dates.

  • NewsList: Essentials News List component.

  • NewsItem: Used by the NewsList component to render the individual news items of the list. Also wrapped in ContentComponentWrapper for in-context editing.

3. Have SPA/React power preview / Channel Manager in CMS

Now that your React components are ready to be managed from the CMS, the next step is to have the React app power the preview / Channel Manager in the CMS. This can be done in two ways: by loading the React app client-side or, when using React server-side, by reverse proxying the app to the BloomReach site.

3.1 Client-side apps

Loading the React app client-side is the easiest to set up and also the recommended approach. However, there might be cases where this is not possible or not wanted. In these cases, please use the approach described in section 3.2, as that approach should work regardless of your React setup.

3.1.1 Update base-layout.ftl

Copy the HTML of your React development server (typically /public/index.html) which loads the React app, and paste this to the base-layout.ftl file in your BloomReach project. The base-layout.ftl is the base HTML template and will be the only template that you’ll ever need to update with this integration approach. You can either copy and paste the HTML directly from the .html file or you can use the server-side response of the Node server provided your React app is loaded client-side (we want the HTML without any React rendered elements).

In case of the example React app, the HTML of public/index.html contains some variables (e.g. %PUBLIC_URL%) that are replaced at runtime by the Node webserver. Copy and paste the HTML to the base-layout.ftl file which is typically found in /repository-data/webfiles/src/main/resources/site/freemarker/PROJECT_NAMESPACE/base-layout.ftl in your BloomReach project folder. Now do a replace all for %PUBLIC_URL% by ${baseUrl}, to change the variable to Freemarker syntax. Then assign the variable by adding the following statement to the top of the template:

<#assign baseUrl="http://localhost:3000"/>

 

Change the hostname and port to match the URL of your React App.

If you now go to the site in BloomReach (http://localhost:8080/site by default) it doesn’t render the React app yet. This is because the node server adds the bundled JavaScript on runtime. Open the source HTML of the React app, and you will see the following line at the bottom:

<script type="text/javascript" src="/static/js/bundle.js"></script>

 

Copy and paste this to the bottom of the base-layout.ftl, right before the closing </body> tag. And prefix the src value with ${baseUrl}, so that it’s changed to:

<script type="text/javascript" src="${baseUrl}/static/js/bundle.js"></script>

 

The React App should now be loaded when viewing the BloomReach site (http://localhost:8080/site by default).

Please note that the above steps might be a little different for your React app depending on how it’s loaded. You will still have to modify the base-layout.ftl file, but where you get the HTML from and what changes you need to make to the HTML might be different.

3.2 Server-side apps

When using a server-side version of React, like next.js or Electrode, the app cannot be loaded via BloomReach’s delivery tier. The best option is to use a reverse proxy, so that all requests to the BloomReach site are routed to the React app. This only has to be done for preview in the CMS and is not needed for the live site.

There is a great NodeJs reverse proxy built by Woonsan Ko that can be used for development environments. For production environments, it is recommended to properly configure your webserver or add a reverse proxy server to your infrastructure.

As described in the instructions of the reverse proxy script, download the script and the package.json and save these somewhere in your BloomReach project folder:

$ curl -L https://raw.github.com/woonsan/hippo7-rproxy-nodejs/hippo7-rproxy-nodejs-1.0.x/rproxy.js > rproxy.js
$ curl -L https://raw.github.com/woonsan/hippo7-rproxy-nodejs/hippo7-rproxy-nodejs-1.0.x/package.json > package.json

 

And run the following command in the folder where you downloaded the files to, to install the required dependencies using Node Package Manager:

$ npm install

 

3.2.1 Run React over https

If you’re running your React app over https, then also follow step 4 of the section How to run the reverse proxy server script of the Reverse Proxy. After this, run the script using the commands of the previous step.

3.2.2 Update reverse proxy mappings

The default mappings of the reverse proxy have to be updated, in order for the reverse proxy to route the requests correctly. We need to route all CMS requests (/cms), internal site (/site/_rp & /site/_targeting) & API requests (/site/resourceapi), and image requests (/site/binaries & /site/images), all to the BloomReach delivery tier. All other URLs need to be routed to the React App.

Copy and paste the mappings below to the rproxy.js file you downloaded earlier:

var mappings = [
  {
    host: '*',
    pathregex: /^\/cms(\/|$)/,
    route: {
      target: 'http://localhost:8080'
    }
  },
  {
    host: '*',
    pathregex: /^\/site(\/_cmsinternal)?\/resourceapi(\/|$)/,
    route: {
      target: 'http://localhost:8080'
    }
  },
  {
    host: '*',
    pathregex: /^\/site\/binaries(\/|$)/,
    route: {
      target: 'http://localhost:8080'
    }
  },
  {
    host: '*',
    pathregex: /^\/site\/images(\/|$)/,
    route: {
      target: 'http://localhost:8080'
    }
  },
  {
    host: '*',
    pathregex: /^\/site\/_rp(\/|$)/,
    route: {
      target: 'http://localhost:8080'
    }
  },
  {
    host: '*',
    pathregex: /^\/site\/_targeting(\/|$)/,
    route: {
      target: 'http://localhost:8080'
    }
  },
  {
    host: '*',
    pathregex: /^\/site\/_cmsinternal\/binaries(\/|$)/,
    route: {
      target: 'http://localhost:8080'
    }
  },
  {
    host: '*',
    pathregex: /^/,
    route: {
      target: 'https://localhost:3000'
    }
  }
];

 

Make sure to update the value of the target properties in case you’re running BloomReach Experience at a different hostname or port. Do the same for the mapping at the bottom, so that it reflects the hostname and port of your React App.

3.3.3 Run the reverse proxy

After updating the mappings, the reverse proxy is ready to be run. Start the script with either:

Mac/Linux:

$ sudo node rproxy.js

 

Windows:

X:\\...> node rproxy.js

 

3.3.4 Update CMS hosts configuration

The reverse proxy script reverse proxies requests to localhost:80 instead of the default localhost:8080, so the hosts configuration of the CMS has to be updated to work with the new URLs. Open the CMS Console and go to the hosts configuration of your development environment, e.g. /hst:hst/hst:hosts/dev-localhost. Change the property hst:cmslocation to http://localhost/cms, and remove the property hst:defaultport (we’re using port 80 which is the default). Finally, don’t forget to write the changes to the repository.

You will have to do the same for other environments. Make sure that you change the hosts' configuration of the corresponding environment and you use the right URLs. Reference the hosts configuration documentation if needed.

You can download the full reverse proxy script with updated mappings here.

3.3 Check CMS Channel Manager

Now that you have either loaded the React app client-side or through a reverse proxy, you should be able to see the React App when opening the Channel Manager in the CMS.

Additionally, any of the components that we defined earlier should show the component overlay and in-context editing, since this is all provided through the component wrapper classes that add the required CMS meta-data through HTML comments in the markup. However, not everything will be working yet, such as adding new components, editing components

The Channel Manager for the example React Demo Project should look as following:

3.4 Defer state updates to React and render CMS authoring overlays

When using BloomReach’s delivery tier, it will handle all changes to the page/state in the Channel Manager, e.g. adding a new component. Because React doesn’t like any other applications to make changes to the DOM, we will need to prevent BloomReach Experience from doing this, and alternatively, have React handle these updates.

Additionally, all of the authoring functionality in the Channel Manager is normally rendered on page-load by parsing the DOM for CMS meta-data contained in HTML comments. When using a client-side SPA/React app, there can be situations where the CMS is parsing the DOM before the SPA has finished rendering the page. That's why for client-side apps you will have to signal to the CMS when the page is done rendering, so that the Channel Manager can start parsing the DOM and render the authoring overlays.

Fortunately, the React library takes care of the above two steps in the CmsPage component. If you want to understand more about how this is done, please check out the Channel Manager SPA API .

3.5 All done!

You should now be all set for the integration! Go check out the awesomeness by going into the Channel Manager and being able to drag-and-drop the React components around, make in-context changes, edit components, personalize components, etc.

Thanks for reading! And don’t hesitate to ask any questions in our community forum.

4. Appendix

4.1 File description for React library

The React library / example React application serves as a library to keep the development effort of the integration minimal. Below is a list of descriptions of each of the files contained in the library. This list ignores any of the standard React application files, and only describes the classes, components and helper functions that are in the src folder.

File / Folder

Function

./index.js

Example Application that uses the library for rendering. Can be used as a reference on how to include the library in a React app. And also contains an example on routing with URL parameters for passing information such as site context-path and preview mode to the library.

./env-vars.js

Environment variables that need to be changed for each environment (if running dev, test, etc.)

./cms-components

Contains all the core components, Essentials components, and any custom component definitions

./cms-components/core

Contains all the core components that are needed for rendering using the Page Model API, such as page, containers, base-component, content-component, placeholders, etc.

./cms-components/core/

container.js

Renders containers and CMS meta-data for container, and loops through nested components of the container. Can add additional HTML here if containers need to be wrapped in certain HTML for layout/styling purposes.

./cms-components/core/

container-item.js

Base component definition that should be used by every component. Renders components and CMS meta-data for components. New components should be added to the switch statement.

./cms-components/core/

content-component-wrapper.js

Base component definition that should be used by components that reference a single content item, e.g. Banner component. Looks up content using reference, and renders component and CMS meta-data for Edit Content button. New components that need this functionality should be added to the switch statement.

./cms-components/core/

page.js

Renders page by fetching Page Model API and rendering containers. This is the component that should be included in the React app to pages that you want BloomReach Experience to power.

./cms-components/core/

placeholder.js

Placeholder for components that have not been configured (e.g. component has just been added to a page)

./cms-components/core/

undefined.js

Any components that are added from within the CMS and have no counterpart in React, will fallback to this component.

/cms-components/

essentials

Examples of Essentials components

./cms-components/

essentials/banner.js

Essentials Banner Component

./cms-components/

essentials/content.js

Essentials Content Component which is used by News detail pages. Not to be confused with Simple Content Component

./cms-components/

essentials/news-item.js

News Item Component to render a news item for the Essentials News List Component

./cms-components/

essentials/news-list.js

Essentials News List Component. Requires News Item Component

./utils

Helper functions

./utils/

cms-meta-data.js

Generates HTML comments based on meta-data provided by Page Model API. The HTML comments are used to provide CMS functionality in Preview Mode, such as in-context editing, drag&drop, etc.

./utils/date.js

Helper function for parsing dates

./utils/fetch.js

Helper functions for fetching Page Model API

./utils/

find-child-by-id.js

Helper function for updating state for an individual component based on ID

./utils/image-url.js

Helper function for generating URLs to images