Contentful

Install Shopstory app in the Contentful

Go to the https://www.contentful.com/marketplace/app/shopstory and install the Shopstory in your space. The last step of the installation is Shopstory configuration:

Fill the "Canvas URL" field with the URL of the canvas page you just created and add a query param with Shopstory access token:

http://localhost:3000/shopstory-canvas?shopstoryAccessToken={YOUR_SHOPSTORY_ACCESS_TOKEN}

Click "Save" to finish the installation process.

Connect Contentful in the front-end code

The last step we need do is to tell our front-end how to connect to Contentful. In order to do it you must use Shopstory Contentful plugin.

Start with installing the Shopstory-Contentful plugin in your project:

npm install @shopstory/contentful

Now go to shopstory/config.ts and add the plugin:

import { Config } from "@shopstory/core";
import { contentfulPlugin } from "@shopstory/contentful";

export const shopstoryConfig : Config = {
  plugins: [contentfulPlugin({
    space: process.env.NEXT_PUBLIC_CONTENTFUL_SPACE!,
    environment: process.env.NEXT_PUBLIC_CONTENTFUL_ENVIRONMENT ?? 'master',
    accessToken: process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN!,
    previewAccessToken: process.env.NEXT_PUBLIC_CONTENTFUL_PREVIEW_ACCESS_TOKEN!
  })]
}

In order to make Shopstory work properly you must pass both preview token and standard Contentful access tokens.

Let's build some content!

During installation of Shopstory Contentful app, a new content type is added to your space: "Shopstory Block". It's a very simple content type representing a piece of visual content.

In order to build your first visual content just create a new "Shopstory Block" entry, click the button "Open visual editor" and build something:

Content modeling with Shopstory

From the content modeling perspective Shopstory Block is just a starting point. It can be referenced by other content types representing for example landing pages or product pages. If you don't like references you can also add Shopstory field directly in your content types. To learn more about content modeling with Shopstory read this guide.

Displaying content

The last step is to render Shopstory content in your project. For the demo purpose we'll create a page that renders Shopstory Block entry by id - pages/shopstory-block/[entryId].tsx:

import type { NextPage, GetStaticProps, GetStaticPaths } from "next";
import {RenderableContent, Metadata, RawContent} from "@shopstory/core";
import { createClient, Entry } from "contentful";
import { ShopstoryClient } from "@shopstory/core";
import { Shopstory, ShopstoryMetadataProvider } from "@shopstory/react";
import { shopstoryConfig } from "../../src/shopstory/config";
import { DemoShopstoryProvider } from "../../src/shopstory/provider";

type ShopstoryBlockPageProps = {
  renderableContent: RenderableContent;
  meta: Metadata;
};

const ShopstoryBlockPage: NextPage<ShopstoryBlockPageProps> = (props) => {
  return (
    <DemoShopstoryProvider>
      <ShopstoryMetadataProvider meta={props.meta}>
        <Shopstory content={props.renderableContent} />
      </ShopstoryMetadataProvider>
    </DemoShopstoryProvider>
  );
};

export const getStaticPaths: GetStaticPaths = () => {
  return { paths: [], fallback: "blocking" };
};

export const getStaticProps: GetStaticProps<
  ShopstoryBlockPageProps,
  { entryId: string }
> = async (context) => {
  let { params, preview, locale = "en-US" } = context;

  if (!params) {
    return { notFound: true };
  }

  const rawContent = await fetchShopstoryContentJSONFromCMS(params.entryId, locale, !!preview);

  const shopstoryClient = new ShopstoryClient(shopstoryConfig, {
    locale,
    contentful: { preview },
  });
  const renderableContent = shopstoryClient.add(rawContent);
  const meta = await shopstoryClient.build();

  return {
    props: { renderableContent, meta },
    revalidate: 10,
  };
};

async function fetchShopstoryContentJSONFromCMS(entryId: string, locale: string, preview: boolean) : Promise<RawContent> {
  const contentfulClient = createClient({
    space: process.env.NEXT_PUBLIC_CONTENTFUL_SPACE!,
    environment: process.env.NEXT_PUBLIC_CONTENTFUL_ENVIRONMENT ?? "master",
    accessToken: preview
      ? process.env.NEXT_PUBLIC_CONTENTFUL_PREVIEW_ACCESS_TOKEN!
      : process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN!,
    host: preview ? "preview.contentful.com" : undefined,
  });

  const entry: Entry<any> = await contentfulClient.getEntry(entryId, {
    content_type: "shopstoryBlock",
    locale,
  });

  return entry.fields.content;
}

export default ShopstoryBlockPage;

Most of the code is CMS-agnostic and relates to how Shopstory SDK works (ShopstoryClient, ShopstoryMetadataProvider, Shopstory, etc). This will be explained in the next chapter: Displaying Content.

However, there are 2 Contentful-specific pieces of code here.

First one is the body of the fetchShopstoryContentJSONFromCMS function. As you can see it's a standard call to Contentful API that fetches Shopstory JSON field content. In this example we fetch the field named content from the built-in Shopstory Block content type. It's a standard Contentful JSON field and we can access its value via entry.fields.content.

The second Contentful-specific piece of code is here:

 const shopstoryClient = new ShopstoryClient(shopstoryConfig, {
    locale,
    contentful: { preview }, // Contentful-specific code
  });

Here we tell the Shopstory-Contentful Plugin if the Contentful resources linked in Shopstory content (media and entries) should be fetched with Content Preview API (preview: true) or Content Delivery API (preview: false).

Further reading

Now it's time to read Displaying Content chapter and understand how Shopstory SDK works.

getStaticPaths implementation is dummy just for the sake of demo simplicity.

Last updated