What you'll learn
  • how to fetch data from Webiny
  • how to parse data including rich text
  • how to use preview mode

Overview
anchor

In this article we’ll learn how to integrate a NextJS application that uses Webiny as a Headless CMS. We’ll go through the following:

  1. Getting started with Webiny
  2. Setting up a NextJS project
  3. Fetching data
  4. Rendering Webiny’s rich text field
  5. Using NextJS preview mode

Getting Started With Webiny
anchor

Webiny works really well as a headless CMS with NextJS external link, especially is that so when using static rendering and the new Preview mode available in NextJS version 9.3.

The easiest way to get started is to use the official starter external link, but we’re going to walk through a similar custom implementation here.

Use the official starter external link for 1-click install on Vercel.

For this tutorial, we’re going to assume you have already done the following:

  1. Set up your AWS credentials locally external link
  2. Deployed a Webiny instance to a dev environment external link

Once you have an app up and running, click into the “HeadlessCMS” app in the sidebar, click on models.

Sidebar - Headless CMS ModelsSidebar - Headless CMS Models
(click to enlarge)

Add the following models and fields: (Skip this step if you already have content models defined)

Authors
anchor

  • A text field with the value “name”
  • A text field with the value “slug” (optionally add a validator using this regex which will make sure you have valid urls: ^(?!.*--)[a-z0-9\-]+$)
  • a files field with the value “picture”

Posts
anchor

  • A text field with the value “title”
  • A text field with the value “slug” (optionally use the regex above as a validator)
  • A files field with the value “featured image”
  • A rich text field with the value “body”
  • A reference field with the value “author”

Next, add some content by going to Headless CMS > Ungrouped and choosing a content model.

Before we spin up our NextJS project, let’s go to the API playground and see how our GraphQL API is structured. In the sidebar menu, choose “API Playground” to open the GraphQL explorer.

GraphQL playgroundGraphQL playground
(click to enlarge)

Notice that there are four APIs listed in the tabs at the top of the page. Choose HeadlessCMS - Read API. From here you can explore your content structure and the schema (via the right side panel). Directly below the tabs is a URL string. That’s the URL you can use to fetch data. Make a note of this URL, you’ll need it soon.

Next, let’s configure API credentials. Choose API Keys in the sidebar. Add an API key with any name and description. Select “Headless CMS” and choose a Custom access level for all content model groups with the values read and preview. Make sure to leave “manage” unchecked. This will help secure our Webiny instance.

If you’re planning to source images from Webiny too, scroll down to the “File Manager” section and enable Custom access to all files with read as the primary action.

Save the API token and the Token itself will be revealed. You can use the same API token for both published and draft posts.

Set Up a NextJS Project
anchor

You can follow the “Getting Started” page on the NextJS site external link, or run npx create-next-app nextjs-blog-webiny --example "https://github.com/webiny/nextjs-starter-webiny/tree/master" to spin up a basic project.

Once you’ve done one of these, add a new file .env.local to the project root. NextJS will look for environment variables in that location.

Add the API endpoint URL (maybe call it NEXT_PUBLIC_WEBINY_API_URL), and the API token (maybe as WEBINY_API_SECRET).

.env.local
NEXT_PUBLIC_WEBINY_API_URL=<your-url-here>
WEBINY_API_SECRET=<your-token-here>

Make sure you don’t commit this file to your project’s git repository otherwise anyone could gain access to your API. You can do that by adding it to the .gitignore file.

Environment variables set up in the project rootEnvironment variables set up in the project root
(click to enlarge)

Fetching Data
anchor

Once you have your .env.local file set up in the project root, you can try to connect to the API so that we can fetch our content.

Create a new folder in the root directory called lib, and within that a new file called api.js. Add this code which will allow us to use JavaScript’s fetch function with the GraphQL API:

/lib/api.js
async function fetchAPI(query, { variables } = {}) {
  const res = await fetch(process.env.NEXT_PUBLIC_WEBINY_API_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.WEBINY_API_SECRET}`
    },
    body: JSON.stringify({
      query,
      variables
    })
  });

  const json = await res.json();
  if (json.errors) {
    console.error(json.errors);
    throw new Error("Failed to fetch API");
  }

  return json.data;
}

You won’t need to add Axios, node-fetch or any other dependency to the project because NextJS polyfills server-side fetch external link.

The POST verb is used to post the query we want our GraphQL API to return. Also please note in the 'Authorization' header, you need to ensure you have Bearer before the API secret. This specifies the method of authentication.

Here’s a basic query you can use to fetch all content for the model posts and return only the slug field:

/lib/api.js
export async function getAllPostsWithSlug() {
  const data = await fetchAPI(`
    query PostSlugs {
      listPosts {
        data {
          slug
        }
      }
    }
  `);
  return data.listPosts.data;
}

Here’s a more detailed query which uses variables and sorting to fetch multiple groups of data:

/lib/api.js
export async function getPostBySlug(slug) {
  const data = await fetchAPI(
    `
      query PostBySlug( $PostsGetWhereInput: PostsGetWhereInput!) {
        post: getPosts( where: $PostsGetWhereInput ) {
          data {
            id
            title
            slug
            description
            createdOn
            featuredImage
            body
            author {
              name
              slug
              picture
            }
          }
        }
        morePosts: listPosts(limit: 2, sort: createdOn_ASC) {
          data {
            id
            title
            slug
            description
            createdOn
            featuredImage
            author {
              name
              picture
      
            }
          }
        }
      }
    `,
    {
      variables: {
        PostsGetWhereInput: {
          slug: slug
        }
      }
    },
    preview
  );
  return data;
}

Hopefully with this information you will be able to use the data from Webiny to render your pages.

To create static pages, create a file in the /posts/ directory called [slug].js, and add this function at the bottom:

/pages/posts/[slug].js
export async function getStaticPaths() {
  const allPosts = await getAllPostsWithSlug();

  return {
    paths: allPosts.map(post => {
      return {
        params: {
          slug: `/posts/${post.slug}`
        }
      };
    }),
    fallback: true
  };
}

This will render all of the posts as static pages.

To get data for each statically generated page add the following to the bottom of the page:

/pages/posts/[slug].js
export async function getStaticProps(context) {
  const { params } = context;
  const data = await getPostBySlug(params.slug);

  return {
    props: {
      preview,
      post: {
        ...data.post.data
      },
      morePosts: data?.morePosts
    }
  };
}

Now you will be able to access props.post in the render and build out your UI:

/pages/posts/[slug].js
export default function Post({ post, morePosts, preview }) {
  return <h1>{post.title}</h1>;
}

You should now be able to see your data! Run yarn dev and open the browser on port 3000, or whichever port you are using.

NextJs blog exampleNextJs blog example
(click to enlarge)

Rendering Rich Text Fields
anchor

You may have noticed that we chose a rich-text field for the body of the post. To render this in React, you can use our rich text renderer package external link:

This package will work with v5.25.0 of Webiny, but for now it’s available with the beta release, so you can install it from the beta:

npm install @webiny/react-rich-text-renderer@beta

Once the package is installed, you can use it as you would any other component:

/pages/posts/[slug].js
// import the renderer
import { RichTextRenderer } from "@webiny/react-rich-text-renderer";

// in your render function
<RichTextRenderer data={content} />;

It’s entirely possible to make your own renderer. If you open that package you’ll see how we have done it, you can copy that code and modify it as you wish.

Using NextJS Preview Mode
anchor

NextJS provides an API for previewing content that hasn’t been published yet, and is in a “Draft” state. For more information, see the documentation on NextJS website external link. Here’s how to use Preview mode with Webiny.

Webiny has a separate API for published and draft content. Although we can use the same API token to connect to both APIs, we need to fetch from the correct API endpoint.

Because we checked preview when we set up the API keys, our NextJS app will be authorised to fetch data from this endpoint too.

In your Webiny instance, go to API Playground and choose Headless CMS - Preview API on the tabs at the top of the main window.

GraphQL playground - preview API URLGraphQL playground - preview API URL
(click to enlarge)

Copy the URL and save that into your .env.local file (perhaps call it NEXT_PUBLIC_WEBINY_PREVIEW_API_URL).

.env.local
NEXT_PUBLIC_WEBINY_PREVIEW_API_URL=<your-url-here>

First, we’re going to extract whether the user has preview mode enabled from the getStaticProps() function:

/pages/posts/[slug].js
export async function getStaticProps(context) {
  const { params, preview } = context;
  const data = await getPostBySlug(params.slug, preview);

  return {
    props: {
      preview,
      post: {
        ...data.post.data
      },
      morePosts: data?.morePosts
    }
  };
}

Now we can pass that variable to our fetchAPI() function

/lib/api.js
export async function getPostBySlug(slug, preview) {
  const data = await fetchAPI(
    `
      query PostBySlug( $PostsGetWhereInput: PostsGetWhereInput!) {
        post: getPosts( where: $PostsGetWhereInput ) {
          data {
            id
            title
            slug
            description
            createdOn
            featuredImage
            body
            author {
              name
              slug
              picture
            }
          }
        }
        morePosts: listPosts(limit: 2, sort: createdOn_ASC) {
          data {
            id
            title
            slug
            description
            createdOn
            featuredImage
            author {
              name
              picture
      
            }
          }
        }
      }
    `,
    {
      variables: {
        PostsGetWhereInput: {
          slug: slug
        }
      }
    },
    preview
  );
  return data;
}

Next modify your fetchAPI function to add the preview parameter:

/pages/posts/[slug].js
async function fetchAPI(query, { variables } = {}, preview) {
  const url = preview
    ? process.env.NEXT_PUBLIC_WEBINY_PREVIEW_API_URL
    : process.env.NEXT_PUBLIC_WEBINY_API_URL;

  const res = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.WEBINY_API_SECRET}`
    },
    body: JSON.stringify({
      query,
      variables
    })
  });

  const json = await res.json();
  if (json.errors) {
    console.error(json.errors);
    throw new Error("Failed to fetch API");
  }

  return json.data;
}

To complete the setup, you can follow the steps from “Securely accessing it from your Headless CMS” on the NextJS documentation site external link.

Once you have those steps set up, visit the API endpoint specified in the docs above. You should be able to see draft posts:

NextJS example - preview modeNextJS example - preview mode
(click to enlarge)

Conclusion
anchor

Using Webiny for a Headless CMS using NextJS is an easy, cost-effective and scalable solution.

As well as the basics we’ve discussed here, you have virtually no limit on the amount of records you store, or even the amount of images you store inside Webiny. You can also have multiple locales to support multiple languages, and other apps are included such as the page builder external link. Also, we have pricing plans that scale external link if you’re a small business owner or enterprise customer.

We hope you enjoy using Webiny to create your next website!