Building the Amplience blog (pt.2): Visualization & preview

Ben Pearey
September 30, 2019
9 mins
Product updatesEngineering

In the previous blog post Building the Amplience blog - Part 1 we covered how we implemented a NextJS blog site using Dynamic Content to drive the data. This blog takes what we did previously and demonstrates how we integrate some additional cool Dynamic Content features.

Visualizing blog content with Dynamic Content

Visualization is a great tool built into Dynamic Content which provides on-demand, side-by-side views of content as it is edited.

Creating a Visualization entry point

To get this working in the blog we needed to provide an entry point (via a URI) that would accept a Virtual Staging Environment (VSE) domain and a content item ID. In 

1next.config.js
 we defined a new export path with the expected page and query string params keys:

1// next.config.js
2
3'/visualization.html': {
4  page: '/visualization',
5  query: {
6    vse: '',
7    content: ''
8  }
9}

Now any request made to

1/visualization.html
will forward
1vse
and
1content
to the
1visualization
NextJS page.

Handling Visualization requests

To handle Visualization requests in the blog app we created a

1VisualizationPage
.

1// pages/visualization.tsx
2import Layout from '../layouts/default';
3import Visualization from '../components/visualization/visualization';
4import { useRouter } from 'next/router';
5
6const VisualizationPage = () => {
7  const router = useRouter();
8  const stagingEnvironment = router.query.vse ? router.query.vse.toString() : '';
9  const contentId = router.query.content ? router.query.content.toString() : '';
10  return (
11    <Layout contentOnly={true}>
12      <Visualization stagingEnvironment={stagingEnvironment} contentId={contentId} />
13    </Layout>
14  );
15};
16
17export default VisualizationPage;

This component is a NextJS page that acts like a top-level controller. It has a primary role of capturing the VSE domain and content ID from the request’s query string. Once these details have been retrieved, it can render the default layout (without a header and footer if you set

1contentOnly
to false) and the
1Visualization
component.

Processing Visualization requests

Now that we have the information we need to generate a Visualization, we can start to render output via the

1Visualization
component. One of the main observations regarding this component is that it is built as a standard React component. This allows us to utilise React’s lifecycle hooks, so we can make client-side requests to the Content Delivery API and get the latest unpublished content.

The component was built to render Visualizations for the following content types:

  • blog-post.json

  • text.json

  • image.json

  • video.json

Whenever the component mounts or updates (

1componentDidMount
or
1componentDidUpdate
) it will attempt to get the latest unpublished content for the supplied content ID.

1// components/visualization.tsx
2
3private async loadContent() {
4    try {
5      const contentItem = await getStagingContentItemById(this.props.stagingEnvironment, this.props.contentId);
6      if (isBlogPost(contentItem)) {
7        this.setState({ blogPost: contentItem });
8      } else {
9        this.setState({ content: [contentItem as AmplienceContent] });
10      }
11    } catch (error) {
12      this.setState({ error: error.message });
13    }
14  }

First we call the Content Delivery API, passing the VSE domain and content ID. We then check to see if the content returned is content (text, image, video) or a blogPost. Once we've established what content we have the component state is set accordingly. With the state set we can render the contents respective component via the React.Component render function.

1// /components/visualization.tsx
2 
3if (this.state.content !== undefined) {
4  return <Content content={this.state.content} />;
5}
6
7if (this.state.blogPost !== undefined) {
8  return <Blog blogPost={this.state.blogPost} />;
9}

Here we check the state to see if either

1content
or
1blogPost
have been defined and render appropriately.

Integrating the blog's visualization into Dynamic Content

Adding the blog’s visualization to a Dynamic Content content type is a straightforward task. Simply:

  1. Login into Dynamic Content

  2. Navigate to: Development > Content Types

  3. Open the desired content type (blog-post, image, text or video)

  4. Click 'Add a visualisation'

  5. Add your visualization to the 'Visualization URI' e.g.

    1https://<your-domain>/visualization.html?vse={{vse.domain}}&content={{content.sys.id}}

  6. Save your changes

Now when you are editing content using that content type you should be able to see your visualization in action.
For more information on configuring a Visualization checkout our docs: [Visualizations Amplience Documentation Hub](https://amplience.com/docs/integration/visualizations.html).

Previewing the blog site using Dynamic Content

Preview is similar to visualizations and is another great tool that helps content authors view content before it is published. While visualizations are great for viewing new unpublished content types, Preview takes it one step further and allows you to look at a snapshot of your entire blog at a given time in the future.

There are two areas to keep in mind when implementing Preview in the blog: Preview for existing routes (updating existing pages or blog posts) and Preview for new routes (new blog posts that are yet to be published)

Previewing existing routes

Previewing existing routes is a fairly straightforward task (this mainly applies to the landing page and already published blog pages). To get the Preview content we can simply update the 

1getInitialProps
 function on the NextJS page to allow us to pass a staging environment to the Content Delivery Service.

1Index.getInitialProps = async ({ query }): Promise<BlogListData> => {
2  const stagingEnvironment = query.vse ? `//${query.vse.toString()}` : undefined;
3  const id: string = process.env.DYNAMIC_CONTENT_REFERENCE_ID || '';
4  try {
5    return getHydratedBlogList(id, stagingEnvironment);
6  } catch (err) {
7    console.error('Unable to get initial props for Index:', err);
8    throw err;
9  }
10};

Here we check to see if a VSE domain has been passed via the props (from the query string). If we have a VSE domain then we pass this on to the service that makes the Content Delivery Service request as the

1stagingEnvironment
. This is used in the Content Delivery Service SDK config and override the default production base URL.

Previewing new routes

When we create a new blog post there is no corresponding export path in our blog, so using the method above would result in a 404 not found error. To allow us to Preview new, unpublished blog pages we need to do things a little differently. Firstly, we define a new export path specifically for Preview content in

1next.config.js
:

1// next.config.js
2
3'/preview': {
4  page: '/preview',
5  query: {
6    content: '',
7    vse: ''
8  }
9}

Once we have the export path setup we need a page to support it. For this we created a

1PreviewPage
NextJS page component.

1const PreviewPage = () => {
2  const router = useRouter();
3  const stagingEnvironment = router.query.vse ? router.query.vse.toString() : '';
4  const contentId = router.query.content ? router.query.content.toString() : '';
5  return (
6    <Layout contentOnly={false}>
7      <Visualization stagingEnvironment={stagingEnvironment} contentId={contentId} />
8    </Layout>
9  );
10};

In a similar way to the Visualization page, we check for the necessary query string values of a VSE domain and content ID. As the PreviewPage is a preview of the entire app we need to display the full layout, so we do not set the contentOnly flag on the layout component. We then render the Visualization component inside the layout. The Visualization component already does everything that we need to get staging content and render a blog post so we can simply re-use that component for Preview.

Maintaining query string values while navigating

When navigating in Preview mode we need to maintain the query string as we click between pages. To do this we need to capture the current query string and construct the anchor tag href to contain the VSE domain.

1// component/blog-card/blog-card.tsx
2
3const router = useRouter();
4const vse = router.query.vse ? router.query.vse.toString() : '';
5const routerQuery = vse ? `?vse=${vse}&content=${blogPost.id}` : '';
6const path = vse ? '/preview' : `/blog/${encodeURIComponent(blogPost.urlSlug.toLowerCase())}`;
7const blogLink = `${path}${routerQuery}`;

Here we use the NextJS

1useRouter
function to grab the current route information, including the query string. From this we can determine whether we are in preview mode (query.vse is present) and build up the path and query string as needed.

Integrating the blog's Preview into Dynamic Content

Adding the blog's Preview to Dynamic Content is simple and can be achieved with the following steps:

  1. Login to Dynamic Content

  2. Navigate to Setting (cog icon) > Preview

  3. Click the blue plus icon to create a new Preview

  4. Give it a name of your choosing and this for the application URL:

    1https://<your-domain>/?vse={{vse.domain}}

When you click the Preview button, the preview is launched, showing what how the blog site will appear on the chosen date.
For further details on Preview see our docs: [Previewing content · Amplience Documentation Hub](https://amplience.com/docs/planning/previewingcontent.html).

What we learned

Being tasked with creating the Amplience Product blog has been a great learning experience for us. It has given us an opportunity to work really closely with JAMStack and discover how it best integrates with Dynamic Content products. Here are some of the things we learned when building the blog site.

Managing dynamic routing and client-side requests

A key feature of NextJS is how it takes a hybrid approach to data loading. In static mode NextJS does the following by default:

  1. On load, present a purely static render of the page (generate at build time)

  2. Re-render the page by executing the pages 

    1getInitialProps
     - depending on how the page is loaded

This allows NextJS to provide lightning fast initial load times but also keeps the pages reactive. This behavior was mostly a benefit, but not in certain scenarios. One area that had us scratching our heads for a while was why it was making client-side requests to our services to get content. Our content would not be changing between builds of the blog so we would be wasting requests getting content we already had.

It turns out using the 

1<Link />
 NextJS components to link between pages utilises the next router which in turn, triggers a call to 
1getInitialProps
 for the linked page (when loaded). There was a lot of digging through the next code base and soul searching to work out what was going on. But in the end the solution was simple: we noticed that loading the pages directly didn't trigger 
1getInitialProps
 and then we had the lightbulb moment. If we switched out the NextJS link components for standard anchor tags we no longer got the unwanted calls of 
1getInitialProps
. To make things a little more reusable we created a 
1<StaticLink />
 component so we could easily change the behaviour of our standard anchor tags.

Get the code

Head over to our GitHub repo to find the complete source code for the blog site, including the preview and visualization functionality that we showed you how to implement in this blog post.