Creating a blog using Contentful

Contentful is a headless content management system that can be used to create, manage, and publish content on any digital channel.

In this article, you'll use content from a Contentful account with your Frontastic project by creating a blog.

Before we start, create a sandbox in your Frontastic studio and use this Frontastic sandbox instance for local development.

Contentful dashboard

Open Contentful, then:

  1. Copy the Space ID and Access token from your Contentful dashboard and save them for later

  1. Create a Blog content model like below

  1. Publish the first Blog Post

Then, update your Frontastic configuration in <customer>_<project>/config/project.yml

...
configuration:
    contentful:
        spaceID: <space-id>
        accessToken: <access-token>
...

Create a blog data source

To fetch blogs from Contentful and pass the data to the components, you need to create a data source.

  1. Create a data source schema in the studio
{
    "customDataSourceType": "contentful/blog",
    "name": "Contentful blog",
    "category": "Documentation examples",
    "icon": "source",
    "schema": [
        {
            "name": "Settings",
            "fields": [
                {
                    "label": "Blog ID",
                    "field": "blogId",
                    "type": "string",
                    "translatable": false
                }
            ]
        }
    ]
}

To do this, open the Data sources area, click Create schema, then copy and paste the above schema into the editor, and click Publish

  1. Install the contentful package by running yarn add contentful inside the <package>/backend folder.
  2. To get the blog data from Contentful Delivery API, implement a getBlogFromId function which takes theblogId and frontasticContext and returns the data fields from Contentful response data.

🚧

You must use require with the contentful package.

const contentful = require('contentful');

const SPACE_ID = 'a2bq7lmfe3nn';

async function getBlogFromId(blogId: string, frontasticContext: Context) {
  const { accessToken } = frontasticContext.project.configuration.contentful;
  try {
    const contentfulClient = contentful.createClient({ space: SPACE_ID, accessToken });
    const response = await contentfulClient.getEntry(blogId);
    return response.fields;
  } catch (e) {
    return e.message;
  }
}

πŸ“˜

This examples uses the Contentful npm package, but you can also use their Content Delivery API.

  1. Implement the contentful/blog data source extension inside the backend/index.ts
'contentful/blog': async (
      config: DataSourceConfiguration,
      context: DataSourceContext,
    ): Promise<DataSourceResult> => {
      const { blogId } = config.configuration;
      if (blogId) {
        const blogData = await getBlogFromId(blogId, context.frontasticContext);
        return {
          dataSourcePayload: blogData,
        };
      }
      return {
        dataSourcePayload: config.configuration,
      };
    },

Create a blog component

To display the blog content from the data received from the data source, you need to create a Frontastic component.

  1. Go to the Components area in the studio, click Create schema, copy the below schema into the JSON editor, and click Publish and download schema

    {
        "tasticType": "contentful/blog",
        "name": "Contentful blog",
        "icon": "list",
        "category": "Documentation examples",
        "schema": [
            {
                "name": "Configuration",
                "fields": [
                    {
                        "label": "Content",
                        "field": "content",
                        "type": "dataSource",
                        "dataSourceType": "contentful/blog",
                        "required": true
                    }
                ]
            }
        ]
    }
    
    
  2. Implement the React component in the /tastics/contentful/blog/index.tsx, to render the markdown content to html.
    You can access the Contentful content in your component in the data property, which is passed to the component at the time of Server Side Rendering by the contentful/blog data source.

import React from 'react';
import { fetcher } from 'frontastic';
import Markdown from 'components/commercetools-ui/content/markdown';

const ContentfulBlog = ({ data }) => {
  return (
    <Markdown
      text={data.content.dataSource.text}
    />
  );
};

export default ContentfulBlog;
{
    "_type": "Frontastic\\Catwalk\\FrontendBundle\\Domain\\Tastic\\Configuration",
    "mobile": true,
    "tablet": true,
    "desktop": true,
    "content": {
        "_type": "Frontastic\\Catwalk\\NextJsBundle\\Domain\\Api\\TasticFieldValue\\DataSourceReference",
        "dataSourceId": "__master",
        "dataSource": {
            "title": "First ever blog",
            "text": "An h1 header\n============\n\nParagraphs are separated by a blank line.\n\n2nd paragraph. *Italic*, **bold**, and `monospace`. \n\nAgain, text is indented 4 spaces. (Put a blank line between each\nterm and  its definition to spread things out more.)\n\nand images can be specified like so:\n\n![example image](https://images.unsplash.com/photo-1550747528-cdb45925b3f7?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=989&q=80 \"An exemplary image\")\n\nAn h2 header\n------------\n\nHere's a numbered list:\n\n 1. first item\n 2. second item\n 3. third item\n\n### An h3 header ###\n\nNow a nested list:\n\n 1. First, get these ingredients:\n\n      * carrots\n      * celery\n      * lentils\n\n 2. Boil some water.\n\n 3. Dump everything in the pot and follow\n    this algorithm:\n\nHere's a link to [a website](http://foo.bar), to a [local\ndoc](local-doc.html), and to a [section heading in the current\ndoc](#an-h2-header). Here's a footnote [^1].\n\nA horizontal rule follows.\n\n***\n"
        }
    }
}
  1. Register this component in the tastics/index.tsx file
import NotFound from './not-found';
import ContentfulBlog from './contentful/blog';

export const tastics = {
  default: NotFound,
  'contentful/blog': ContentfulBlog,
};

Create a blog page in the studio

  1. Open the Site builder in the Frontastic studio and create a new page folder called first-blog

  1. Expand the Data source section and click + Add data source filter

  1. Use the blog Entry Id from Contentful dashboard as the Blog ID field and click Save

  1. Create a new page version called first inside the first-blog page folder

  1. Add a layout element to your page and then drag the Contentful blog component into it

  1. Select the First blog data source filter created in the previous step in the component settings

You can click Preview to see your page already.

  1. Click Save

  2. Hover on the page version, click the more icon, and select Make default

You should see the page live on <http://localhost:3000/doc-examples/first-blog>

:tada: Yay! You've successfully built a blog page using the data from Contentful.

In a real world scenario, configuring each page with a different blog ID will get tedious and you'd want to render the blog content dynamically based on a URL path. For this, you can use Frontastic dynamic pages to dynamically render a page based on the requested URL.

Let's extend this example to dynamically render a blog based on the page request URL.

Create the dynamic page in the studio

  1. Create a schema for the blog dynamic page in Dynamic pages within the Developer area of the studio

    {
        "dynamicPageType": "contentful/blog",
        "name": "Contentful blog",
        "category": "Documentation examples",
        "icon": "list",
        "dataSourceType": "contentful/blog",
        "isMultiple": true
    }
    

πŸ“˜

Data source in dynamic pages

Providing dataSourceType in a dynamic page schema is an optimization for fetching the dataSourcePayload at the time of dynamic page resolution. In this example, the dynamic-page-handler is expected to send the dataSourcePayload for the contentful/blog data source, and at the time of Server Side Rendering it'll directly be passed to the components which specify contentful/blog as the data source. By doing so, we save a network request making the whole process faster.

  1. Go to the Dynamic pages area using the left-hand navigation, select Contentful blog, then the Default page, then click create New page version (we'll call ours start)

  1. Add a layout element to your page, then add the Contentful blog component, select the contentful/blog data source filter, and click Save

  1. Hover on the start page version and click the more icon, then select Make default

Implement the blog dynamic page

The dynamic page should match the URL <your-site>/contentful/blog/<blogId>, fetch the content for the blogId from Contentful API, and render the markdown content on the page.

  1. Implement the dynamic-page-handler extension in backend/index.ts to fetch the content for blogId from Contentful. For this, you can re-use the getBlogFromId function to keep the dataSourcePayload consistent.
import {
  ActionContext,
  DataSourceConfiguration,
  DataSourceContext,
  DataSourceResult,
  DynamicPageSuccessResult,
  Request,
  Response,
} from '@frontastic/extension-types';
import axios from 'axios';

const contentful = require('contentful');

const SPACE_ID = 'a2bq7lmfe3nn';

async function getBlogFromId(blogId: string, frontasticContext: Context) {
  const { accessToken } = frontasticContext.project.configuration.contentful;
  try {
    const contentfulClient = contentful.createClient({ space: SPACE_ID, accessToken });
    const response = await contentfulClient.getEntry(blogId);
    return response.fields;
  } catch (e) {
    return e.message;
  }
}

export default {
  'dynamic-page-handler': async (request: Request): Promise<DynamicPageSuccessResult | null> => {
    const [_, blogId] = request.query.path.match(new RegExp('/contentful/blog/([^ /]+)'));
    if (blogId) {
      const blogData = await getBlogFromId(blogId, context.frontasticContext);
      return {
        dynamicPageType: 'contentful/blog',
        dataSourcePayload: blogData,
      };
    }
  },
  'data-sources': {
    'contentful/blog': async (config: DataSourceConfiguration, context: DataSourceContext): Promise<DataSourceResult> => {
      const { blogId } = config.configuration;
      if (blogId) {
        const blogData = await getBlogFromId(blogId, context.frontasticContext);
        return {
          dataSourcePayload: blogData,
        };
      }
    },
  }
};
  1. Open <http://localhost:3000/contentful/blog/6EK4wrX5cyT8GKXEs2NxBJ> in your browser, and you should see a page like shown in the image below

πŸ‘

It's an excellent example of how our extensions are implementation agnostic β€” developers can implement any logic they want, be it a REST API call or GraphQL query or even using some SDK. This gives developers the freedom to work around such problems and choose the solution that works for their project.


Did this page help you?