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:
Login into Dynamic Content
Navigate to: Development > Content Types
Open the desired content type (blog-post, image, text or video)
Click 'Add a visualisation'
Add your visualization to the 'Visualization URI' e.g.
1https://<your-domain>/visualization.html?vse={{vse.domain}}&content={{content.sys.id}}
Save your changes
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:
Login to Dynamic Content
Navigate to Setting (cog icon) > Preview
Click the blue plus icon to create a new Preview
Give it a name of your choosing and this for the application URL:
1https://<your-domain>/?vse={{vse.domain}}
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:
On load, present a purely static render of the page (generate at build time)
Re-render the page by executing the pages
- depending on how the page is loaded1getInitialProps
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.