SDK

Next.js Toolkit

The official Next.js toolkit for Contento

This package primarily allows you to integrate with the Contento Visual Preview in your Next.js project.

For more detailed instructions on using the SDK please see the core JavaScript Client docs.

In due course we will also be adding helpers for our first-party SEO tool and Assets.

App Router

Please note that this package currently supports Next.js 13 projects using the App Router.

Installation

npm install @gocontento/next

Visual Preview

Contento uses Next.js 13's new draft mode to enable previewing content from the Contento editor.

Configure the Contento site

Step one is to switch on the Visual Preview tool in Contento. You can do this from the settings tab of the CMS, or from the sites screen click the cog dropdown on your site, and then edit. Once in the site settings, head to Preview in the sidebar and switch it on.

Copy the secret key you can see on this page, as you’ll want it in the next step.

Set up the Contento Client

You can do this in a few different ways, but we recommend creating a factory function that creates the client. This allows for client config to be defined in one place then reused in both server and client components of your Next.js project.

Read the client docs for the full details, but we’ll assume you’re using TypeScript and have the following two files for the rest of this guide.

.env

CONTENTO_API_URL=https://app.contento.io/api/v1
CONTENTO_API_KEY=your_api_key
CONTENTO_SITE_ID=your_site_id
CONTENTO_PREVIEW_SECRET=your_preview_secret_key

lib/contento.ts

import { Client } from "@gocontento/next";

export function createClient(isPreview: boolean = false) {
    return Client.createContentoClient({
        apiURL: process.env.CONTENTO_API_URL ?? "",
        apiKey: process.env.CONTENTO_API_KEY ?? "",
        siteId: process.env.CONTENTO_SITE_ID ?? "",
        isPreview: isPreview,
    });
}

Note that we can access the full Client without having to install the root package, but you can install that too if you want deeper Typescript support or to export its other core methods.

Define the preview route

Next, create an api route in your project called draft, add the route handler file (route.ts) and add the following code to it:

app/api/draft/route.ts

import { createClient } from "@/lib/contento";
import { enableDraftAndRedirect } from "@gocontento/next";

export async function GET(request: Request){
  // Here we are creating a client with the preview flag set to true - this way the Contento editor will be able to
  // validate draft content properly before starting the preview session
  const client = createClient(true);
  return enableDraftAndRedirect(client, request, process.env.CONTENTO_PREVIEW_SECRET ?? "");
}

Here we are using the enableDraftAndRedirect function to enable the Next.js draft mode and redirect to the page we want to preview. This route will be called by the Contento editor when previewing content.

Use the Preview Bridge

To allow the Contento editor to communicate with your Next.js project we need to add the PreviewBridge component to your project’s root layout file.

This allows the Contento editor to refresh the preview when content is updated.

Example

import { PreviewBridge } from "@gocontento/next";
import { draftMode } from "next/headers";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>
        <PreviewBridge draftMode={draftMode().isEnabled} />
        {children}
      </body>
    </html>
  )
}

The draftMode prop must be passed to ensure the <PreviewBridge /> component is only active in draft mode.

Fetching content

To fetch content from Contento, simply create a client instance from your factory function in your page component.

For example, a typical blog index page might look something like this:

import { createClient } from "@/lib/contento";
import BlogPostCard from "@/app/components/blog-post-card";

export default async function Page() {
  const client = createClient();
  
  let response = await client.getContentByType({
      contentType: "blog_post",
      sortBy: "published_at",
      sortDirection: "desc"
  })
  
  let content = [...response.content];
  
  while(response.nextPage){
    response = await response.nextPage();
    content = content.concat(response.content);
  }

  return (
    <div className="max-w-2xl mx-auto">
      {content.map((item) => {
        <BlogPostCard key={item.id} content={item} />        
      })}
    </div>
  )
}

Visual Preview will now refresh whenever we update content in the Contento editor. For content to update in realtime we will need to enable Live Preview by following the steps below.


Live Preview

Live Preview is similar to the Visual Preview detailed above, but it allows editors to see their changes reflected in realtime. For the basic Visual Preview to work, editors have to save the content they are working on, whereas with Live Preview they don’t need to do that in order to see changes.

To enable Live Preview, we still need the basic Visual Preview to be up and running first, and then we need to add a few more things to our project.

The useLivePreview hook

For our preview to update in realtime we need to introduce another hook into our project. The useLivePreview hook is initialised once with content from the Contento API. After that it listens for new content sent locally via messages from the Contento editor.

The hook can only be used in a client side component, so you may need to restructure your components to make use of it. It takes one parameter, which is the content you are making available to the Live Preview engine:

const { content } = useLivePreview({ content: initialContent });

Example

Here is how a blog post page might look. First we fetch the content as normal, using the Next.js draftMode() method to allow the Contento client to enter draft mode properly.

app/blog/[slug]/page.tsx

import { createClient } from "@/lib/contento";
import { draftMode } from 'next/headers';
import { notFound } from "next/navigation";
import BlogDetail from "@/app/components/pages/blog-detail";

export async function generateStaticParams() {
  // ...
}

export default async function page({ params }: { params: { slug: string; } }) {
    const post = await createClient(draftMode().isEnabled)
        .getContentBySlug(params.slug, "blog_post")
        .catch(() => {
            notFound();
        });
    
    return (
        <BlogDetail initialContent={post} />
    );
}

Then, in our client side BlogDetail component we can use the useLivePreview hook to update the content in realtime.

app/components/pages/blog-detail.tsx

"use client";

import { ContentData } from "@gocontento/client/lib/types";
import { useLivePreview } from "@gocontento/next";

export default function BlogDetail({ initialContent }: { initialContent: ContentData }){
    const { content } = useLivePreview({ content: initialContent});
    return (
        <article>
            <header>
                <h1>{content.fields.title.text}</h1>
            </header>
            
            {/* ... */}
        </article>
    );
}

That’s it! Now when you have the preview panel open in the Contento editor, if you change something in a field it will update in realtime.

Previous
JavaScript Client