Measurable Success «10 Checkpoints for Future-Proof Enterprise CMS» White Paper
Measurable Success - White Paper

Integrating Blogposts from HubSpot

Many blogging platforms or the CMSs they are based on offer an API for fetching articles, making it possible to integrate them into websites. So if you have been publishing interesting content on Google’s blogger.com, medium.com, on HubSpot, a WordPress-based website, etc., here’s how to retrieve your articles and display them on your Scrivito-powered site.

The main goal of this tutorial is to illustrate the general working principle of consuming content from a platform: Fetch the data using the provided API, store and process this data, and render it so that it becomes useful to the visitor.

We will use HubSpot’s API to fetch articles that were created using their blogging tool. For displaying previews of them, we will build a blogpost overview widget, and link the articles individually to a dedicated page on which a blogpost widget then renders the full post.

Like with HubSpot, you might need an API key for your source platform in order to fetch the desired data. For natural reasons, you would never expose such a key in your app but use, for example, an AWS Lambda function as a proxy instead.

Are you ready? Let’s start.

The “HubspotBlogOverviewWidget”

As usual, we will provide you with the widget class, component, and editing configuration for the new widget.

The widget class

All the widget needs to know (and the editor will be enabled to specify) is the number of posts to retrieve so the widget class is simple:

src/Widgets/HubspotBlogOverviewWidget/HubspotBlogOverviewWidgetClass.js
import * as Scrivito from "scrivito";

const HubspotBlogOverviewWidget = Scrivito.provideWidgetClass(
  "HubspotBlogOverviewWidget",
  {
    attributes: {
      numberOfPosts: ["enum", { values: ["1", "2", "3", "5", "10"] }],
    },
  }
);

export default HubspotBlogOverviewWidget;

The widget component

Our widget component fetches the specified number of posts when the component has mounted or updated. Our API call for this is asynchronous, and we store the response (delivered in JSON format) in a state variable, making it available to the rendering function. To prevent an infinite loop when updating state, we only fetch the posts if their specified number has changed.

The API request format for fetching your blogposts from HubSpot is
https://api.hubapi.com/content/api/v2/blog-posts/?hapikey=your-api-key. The maximum number of posts to retrieve can be limited using the limit URL parameter (the default is 20). We’ll use the value of our widget’s numberOfPosts attribute for this.

src/Widgets/HubspotBlogOverviewWidget/HubspotBlogOverviewWidgetComponent.js
import * as React from "react";
import * as Scrivito from "scrivito";
import InPlaceEditingPlaceholder from "../../Components/InPlaceEditingPlaceholder";
import axios from "axios";

class HubspotBlogOverview extends React.Component {
  constructor(props) {
    super(props);
    this.widget = this.props.widget;
    this.state = {
      articles: [],
      numberOfPosts: null,
    };
  }

  async getArticles() {
    const numberOfPosts = await Scrivito.load(() =>
      this.widget.get("numberOfPosts")
    );
    if (numberOfPosts === this.state.numberOfPosts) return;
    const response = await axios.get(
      `https://your-hubspot-proxy.com/get-recent-posts?limit=${numberOfPosts}`
    );
    this.setState({ articles: response.data.objects, numberOfPosts });
  }

  componentDidMount() {
    this.getArticles();
  }

  componentDidUpdate() {
    this.getArticles();
  }

  formatDate(seconds) {
    return new Date(seconds).toISOString().slice(-24, -14);
  }

  render() {
    if (this.state.articles.length === 0) {
      return (
        <InPlaceEditingPlaceholder>
          Provide the number of posts in the widget properties.
        </InPlaceEditingPlaceholder>
      );
    }

    return (
      <div>
        {this.state.articles.map((article, index) => (
          <div className="row" key={index}>
            <div className="col col-md-4">
              <img src={`${article.featured_image}`} />
            </div>
            <div className="col col-md-8">
              <h2 className="h5">
                <Scrivito.LinkTag
                  to={Scrivito.Obj.getByPermalink("blogpost")}
                  params={{ id: article.analytics_page_id }}
                  target="_blank"
                >
                  {article.page_title}
                </Scrivito.LinkTag>
              </h2>
              <p>
                {article.author_name} | {this.formatDate(article.author_at)}
              </p>
              <div
                dangerouslySetInnerHTML={{ __html: article.meta.post_summary }}
              />
            </div>
          </div>
        ))}
      </div>
    );
  }
}

Scrivito.provideComponent("HubspotBlogOverviewWidget", HubspotBlogOverview);

In essence, we iterate the articles and render some of their properties: featured_image, page_title, author_name, author_at, and meta.post_summary. The page title is linked to the page whose permalink is “blogpost”, and the post’s analytics_page_id is added as a parameter to the URL. The latter will be used by our second widget below, the blog post widget, to request the article with this specific ID.

The widget’s editing configuration

To finalize our widget, let’s add the editing configuration to it. It simply provides the numberOfPosts attibute’s title and description, and sets the initial number of posts to 3.

src/Widgets/HubspotBlogOverviewWidget/HubspotBlogOverviewWidgetEditingConfig.js
import * as Scrivito from "scrivito";

Scrivito.provideEditingConfig("HubspotBlogOverviewWidget", {
  title: "Hubspot Blog Overview",
  attributes: {
    numberOfPosts: {
      title: "Number of posts",
      description: "Maximum number of blogpost previews to display",
    },
  },
  properties: ["numberOfPosts"],
  initialContent: {
    numberOfPosts: "3",
  },
});

The “HubspotBlogpostWidget”

As indicated above, clicking one of the previews in the overview causes the “blogpost” page to be opened and the post’s ID to be passed in as a URL parameter, id. So the first thing to do would be to create a regular page and assign it the “blogpost” permalink.

Next, let’s create the widget.

The widget class

This, again, is standard. We want editors to be able to specify the page elements to be displayed. For this, we provide a corresponding attribute, elements:

src/Widgets/HubspotBlogpostWidget/HubspotBlogpostWidgetClass.js
import * as Scrivito from "scrivito";

const HubspotBlogpostWidget = Scrivito.provideWidgetClass(
  "HubspotBlogpostWidget",
  {
    attributes: {
      elements: [
        "multienum",
        { values: ["headline", "author", "image", "body"] },
      ],
    },
  }
);

export default HubspotBlogpostWidget;

The widget component

Like with our overview widget above, the blogpost widget fetches the article asynchronously when the component has mounted. HubSpot’s API request format for fetching a single article by its ID is https://api.hubapi.com/content/api/v2/blog-posts/1234567?hapikey=your-api-key.

src/Widgets/HubspotBlogpostWidget/HubspotBlogpostWidgetComponent.js
import * as React from "react";
import * as Scrivito from "scrivito";
import InPlaceEditingPlaceholder from "../../Components/InPlaceEditingPlaceholder";
import axios from "axios";

class HubspotBlogpost extends React.Component {
  constructor(props) {
    super(props);
    this.widget = this.props.widget;
    this.state = { article: null };
  }

  async getArticle() {
    const urlParams = new URLSearchParams(window.location.search);
    const articleId = urlParams.get("id");
    if (articleId === "") return;
    const response = await axios.get(
      `https://your-hubspot-proxy.com/get-article/${articleId}`
    );
    this.setState({ article: response.data });
  }

  componentDidMount() {
    this.getArticle();
  }

  render() {
    if (this.state.article) {
      const elements = this.widget.get("elements");
      return (
        <div>
          {elements.includes("headline") && (
            <h1 className="h3">{this.state.article.page_title}</h1>
          )}
          {elements.includes("author") && (
            <p>by {this.state.article.author_name}</p>
          )}
          {elements.includes("image") && (
            <div>
              <img src={`${this.state.article.featured_image}`} />
            </div>
          )}
          {elements.includes("body") && (
            <div
              dangerouslySetInnerHTML={{
                __html: this.state.article.post_body,
              }}
            />
          )}
        </div>
      );
    }
  }
}

Scrivito.provideComponent("HubspotBlogpostWidget", HubspotBlogpost);

After the requested article has become available, i.e. has been stored in the article state variable, its properties are rendered according to the selected page elements mentioned above.

That’s all there is to it.

Now, add this widget to your “blogpost” page, and click an article’s headline on the overview page. You should then see the full article on a new browser tab. :) If you don’t, just add the widget’s editing configuration and set the desired elements in its properties.

The widget’s editing configuration

For completeness, here’s the widget’s editing configuration:

src/Widgets/HubspotBlogpostWidget/HubspotBlogpostWidgetEditingConfig.js
import * as Scrivito from "scrivito";

Scrivito.provideEditingConfig("HubspotBlogpostWidget", {
  title: "Hubspot Blogpost",
  attributes: {
    elements: {
      title: "Page elements",
      description: "Choose the article elements you want to have displayed.",
      values: [
        { value: "headline", title: "Headline" },
        { value: "author", title: "Author" },
        { value: "image", title: "Image" },
        { value: "body", title: "Body" },
      ],
    },
  },
  initialContent: {
    elements: ["headline", "author", "image", "body"],
  },
  properties: ["elements"],
});

Final words

Integrating third-party services like HubSpot blogposts is not a single-use exercise. What we’ve expanded on here is representative of many other applications in which external data is made available via an API. Whether it’s about supplementing content or points of data from other sources, or integrating remote functionality – the steps are the same.

Regarding our above use case, consider changing the layout, making further blogpost properties selectable and rendering them, for example. Happy coding!