Shopstory
Search…
⌃K

Getting started (next.js + Contentful)

Required tokens

In order to start the process you will need 2 tokens:
  1. 1.
    npm token to install the Shopstory SDK in your front-end (our packages are private only temporarily, we will make them public soon).
  2. 2.
    Shopstory access token.
Please contact Shopstory team ([email protected]) to get the tokens.

Install package

Before you install the shopstory packages please make sure that npm token provided to you by Shopstory team is active. Usually it requires adding following line to ~/.npmrc file:
//registry.npmjs.org/:_authToken={YOUR_NPM_TOKEN}
Start with installing Shopstory SDK in your project:
npm install @shopstory/[email protected]

Add the configuration files

Add the main Shopstory configuration file in src/shopstory/config.ts:
import { Config } from "@shopstory/core"
export const shopstoryConfig : Config = {
}
We're gonna populate it soon but for now let's keep it empty.
Another file we'll need is a custom ShopstoryProvider. Let's create src/shopstory/provider.ts:
import {
ShopstoryProvider,
} from "@shopstory/core/react";
export const DemoShopstoryProvider: React.FC = ({ children }) => {
return (
<ShopstoryProvider>
{children}
</ShopstoryProvider>
);
};
We'll use this component later to pass all the references to custom components and actions used by Shopstory.
It's a good practice to have a dedicated shopstory/ directory for all Shopstory configuration.

Create a canvas page

In order to use Shopstory in your project you must create a canvas page. A canvas page is simply a blank page in your project that Shopstory will use to render content into.
Let's add a canvas page to pages/shopstory-canvas.tsx:
import type { NextPage } from 'next'
import { Canvas } from "@shopstory/core/react";
import { shopstoryConfig } from "../src/shopstory/config";
import { DemoShopstoryProvider } from "../src/shopstory/provider";
const ShopstoryCanvasPage: NextPage = () => {
return <DemoShopstoryProvider>
<Canvas config={shopstoryConfig} />
</DemoShopstoryProvider>
}
export default ShopstoryCanvasPage
<Canvas /> component must be wrapped with our <DemoShopstoryProvider>.
Important! Canvas page should be stripped from any elements like menu, footer, cookie messages, etc. It should be as empty as you can make it, without any margins, paddings, etc.
Now open your next.js application and go to localhost:3000/shopstory-canvas. You should see Shopstory editor in a "playground mode". Playground mode means that Shopstory editor is opened outside of CMS. Playground mode is great for developers to test the Shopstory configuration: custom components, actions, design tokens etc. However, it comes with limitations:
  1. 1.
    You can only use mock image / video pickers. You don't have access to built-in CMS entry / media pickers.
  2. 2.
    The content you build is not saved anywhere.

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. Go to shopstory/config.ts and add Contentful plugin:
import { Config } from "@shopstory/core";
import { contentfulPlugin } from "@shopstory/core/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 } from "@shopstory/core";
import { createClient, Entry } from 'contentful'
import { ShopstoryClient } from "@shopstory/core/client";
import { Shopstory, ShopstoryMetadataProvider } from "@shopstory/core/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 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(params.entryId, {
content_type: 'shopstoryBlock',
locale,
});
const shopstoryClient = new ShopstoryClient(shopstoryConfig, { locale, contentful: { preview } });
const renderableContent = shopstoryClient.add(entry.fields.content);
const meta = await shopstoryClient.build();
return {
props: { renderableContent, meta },
revalidate: 10
}
}
export default ShopstoryBlockPage
The beginning of getStaticProps is a typical code for fetching the entry by id using Contentful SDK. getStaticPaths implementation is dummy just for the sake of demo simplicity.

Building content - ShopstoryClient

The visual content built with Shopstory is stored in the Contentful JSON field. In Shopstory Block content type this field is named content. It can be accessed in our code via entry.fields.content.
The content of the Shopstory JSON field is called "raw content". Raw content is not ready to be sent to the browser. It's not optimised for rendering and 3rd party resources used in the content (media, other entries, products, etc) haven't been fetched yet. Raw content must go through the "build phase" in order to become ready to be sent to the browser and rendered. Content building is handled by ShopstoryClient.

Create ShopstoryClient instance

const shopstoryClient = new ShopstoryClient(shopstoryConfig, { locale, contentful: { preview } });
Content building starts with creating ShopstoryClient instance. First parameter is the Shopstory configuration object. The second one are the "context parameters". locale is mandatory and should be compatible with locales available in Contentful. contentful.preview tells Contentful plugin if preview API should be used.

Add the content

const renderableContent = shopstoryClient.add(entry.fields.config);
In this line we tell our Shopstory client that we want to build the entry.fields.content JSON from the CMS. You can add as many content pieces as you need, in this example we add only one. The output of the add function is called Renderable Content. It's optimised for rendering and can be safely sent to the browser. It's very important to know that when you call add you only stage a content piece to be built. The result object is not "hydrated" until you call shopstoryClient.build(). Thanks to this mechanism you can add multiple Shopstory content pieces and then fetch resources for them in a single API call.

Build

const meta = await shopstoryClient.build();
In this line we do the actual build. All the added content blocks are hydrated and all the resources are fetched.
The result of the build function is called "metadata". Metadata contains all the fetched resources and information about components used by the content. Both Metadata and Renderable Content objects are required to render the content in the browser.

Rendering the content

Rendering content is trivial:
const ShopstoryBlockPage: NextPage<ShopstoryBlockPageProps> = (props) => {
return <DemoShopstoryProvider>
<ShopstoryMetadataProvider meta={props.meta}>
<Shopstory content={props.renderableContent} />
</ShopstoryMetadataProvider>
</DemoShopstoryProvider>
}
We simply pass Renderable Content object to the Shopstory component. Please remember that it must be wrapped with <ShopstoryMetadataProvider /> (which takes Metadata as a prop).

Server-side rendering

The last step is to configure SSR so that you don't experience FOUC (flash of unstyled content). Go to pages/_document.ts and use shopstoryGetStyleTag function:
import Document, { Html, Head, Main, NextScript } from 'next/document'
import React from 'react'
import { shopstoryGetStyleTag } from '@shopstory/core/react'
class MyDocument extends Document {
render() {
return (
<Html>
<Head>{shopstoryGetStyleTag()}</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument

Summary

Congratulations! You made your first steps with Shopstory. You can already use it but in order to reap full benefits there's still some more configuration required.