Next.js x caisy

The final code of this demo is available at

You could use regular JavaScript for this example, but we suggest using TypeScript to fully take advantage of the type safety and autocompletion features offered by a GraphQL API.

Why use Next.js instead of Create React App for a Jamstack website?

Next.js is a React framework that simplifies the creation of server-rendered React applications, which is particularly useful for Jamstack websites. Next.js can create both static and server-rendered pages, resulting in high-performance pages with fast load times. Next.js also offers built-in features for development and deployment, such as automatic code splitting and search engine optimization capabilities. Overall, using Next.js for a Jamstack website can lead to improved performance and simpler development.

Prerequisites

In this example, we continue building on the blog article created in the quick start. If you haven't completed the quick start yet, it is recommended to go through the first three steps again.

To proceed, the following query should retrieve the article data using the slug of the previously created article

query allBlogArticle($slug: String) {
  allBlogArticle(where: { slug: { eq: $slug } }) {
    edges {
      node {
        text {
          json
        }
        title
        slug
        id
      }
    }
  }
}

Create Next.js Project

To create a new Next.js project, run the following command:

npx create-next-app caisy-example-next --ts
cd caisy-example-next
npm install
npm run dev

"Hello world" page

To get started, when creating a new Next.js project, you will find something already on the index page in pages/index.tsx. In the main block, we can add a link to our blog page so that we can click on the link in our browser from the index page.

<Link href="/blog/my-first-article">a link to our first blog page</Link>

The Link is the build in Next.js link component, which you can import with the following line:

import Link from 'next/link'

If you used a different slug than 'my-first-article' in the quickstart, please use that instead.

If the development server is running, you should see a link on your index page. However, clicking the link will result in a 404 page.

Dynamic Page

Let's fix this by creating a dynamic routed page that handles all of our blog pages.
To do this, create a file called [slug].tsx in the path src/pages/blog and in this file we will, configure the path to generate the static page by doing so:

src/pages/blog/[slug].tsx
export default function BlogPage() {
    return <h1>Hello World</h1>
}

Now, if we click on our link, we should at least see a headline saying 'Hello World'.

Data fetching

To retrieve data from the external API comfortably, we use GraphQL with graphql-request as the GraphQL client. You can install graphql-request and graphql by running the following command in your terminal or command prompt:

npm i --save graphql-request graphql

To use the external GraphQL API from caisy, you need to know your project ID and have an API key ready to use. To keep these credentials secure, it's best to store them in the .env.local file in your Next.js project. If this file doesn't exist yet, you'll need to create it.

.env.local
CAISY_PROJECT_ID=54026eaa-a8fc-49f9-ac37-ebe5148c4936
CAISY_API_KEY=Q1Bi5yudIr4HxQEtVt2fmalZRyndjxDW

In the navigation menu, go to Settings->Global to find your project id. Then, under Settings->Development, you can create API keys.

If you need information on how to create an API Key in your project, please refer to:

If you have a .env.local file, Next.js will automatically load the environment variables for you. You can access them in your code using process.env.CAISY_PROJECT_ID, for example. Now, you can set up a GraphQL client with graphql-request using both environment variables. Here's an example:

const client = new GraphQLClient(
    `https://cloud.caisy.io/api/e/v4/${process.env.CAISY_ID}/graphql`,
    {
      headers: {
        "x-caisy-apikey": process.env.CAISY_API_KEY!,
      },
    }
);

For this example to work, you need to replace both variables with your own values. The values shown above are just placeholders and will not work as is.

Now let's use this client to fetch data. To achieve the best site performance and not expose the credentials, we want to fetch the data from caisy on the server-side code of Next.js. Next.js has multiple pre-built methods for this. In this example, we will use ISR (Incremental Static Rendering) as a strategy for server-side rendering.

To use ISR in Next.js, we need to export a function called getStaticProps on our page. For dynamic pages like our blog page, which use the same page for multiple paths (in this case, slugs), we also need to export the function getStaticPaths to define which paths should be pre-rendered and if we should fallback to on-demand server-side rendering. So implementing the getStaticProps and getStaticPaths using the GraphQL client with the queries from the quick start might look this:

pages/blog/[slug].tsx
import { gql, GraphQLClient } from "graphql-request";
import { GetStaticProps } from "next";

const client = new GraphQLClient(
  `https://cloud.caisy.io/api/e/v4/${process.env.CAISY_PROJECT_ID}/graphql`,
  {
    headers: {
      "x-caisy-apikey": process.env.CAISY_API_KEY!,
    },
  }
);

export default function BlogPage({ title }: { title: string }) {
  return <h1>{title}</h1>;
}

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const gqlResponse = await client.request(
    gql`
      query allBlogArticle($slug: String) {
        allBlogArticle(where: { slug: { eq: $slug } }) {
          edges {
            node {
              text {
                json
              }
              title
              slug
              id
            }
          }
        }
      }
    `,
    { slug: params?.slug }
  );
 
  return {
    props: gqlResponse?.allBlogArticle?.edges?.[0]?.node || {},
  };
};

export async function getStaticPaths() {
  const gqlResponse = await client.request(
    gql`
      query {
        allBlogArticle {
          edges {
            node {
              slug
            }
          }
        }
      }
    `
  );

  const paths: { params: { slug: string } }[] = [];

  gqlResponse?.allBlogArticle?.edges?.forEach(
    (edge: { node: { slug?: string } }) => {
      if (edge?.node?.slug) {
        paths.push({ params: { slug: edge.node.slug } });
      }
    }
  );

  return {
    paths,
    fallback: true,
  };
}

In this code snippet, we first set up the GraphQL client and assign it to a const called "client". This client is used in both "getStaticProps" and "getStaticPaths".

"getStaticPaths" fetch all the latest published blog articles and returns the slugs as params so Next.js can use them to pre-render the pages on build.

"getStaticProps" uses the parameter "slug" that it gets from the Next.js router and passes it down to the GraphQL query. The query will fetch the blog article that matches that slug. The resulting blog article properties are returned to the page.

In the default export, the page, we use the title property of the blog article to be rendered as the headline.

Now, with this code in place, when testing the example, you should see the title of your blog article.

Rendering the rich text

We've fetched the data, but the UI still only displays only the title and missing the rich text of our blog article. To render the rich text use the pre-built library @caisy/rich-text-react-renderer , which can be installed with a package manager like this:

npm i --save @caisy/rich-text-react-renderer

It can be used in the page's template file like this:

pages/blog/[slug].tsx
import { gql, GraphQLClient } from "graphql-request";
import { GetStaticProps } from "next";
import { RichTextRenderer } from "@caisy/rich-text-react-renderer";

const client = new GraphQLClient(
  `https://cloud.caisy.io/api/e/v4/${process.env.CAISY_PROJECT_ID}/graphql`,
  {
    headers: {
      "x-caisy-apikey": process.env.CAISY_API_KEY!,
    },
  }
);

export default function BlogPage({
  title,
  text,
}: {
  title: string;
  text: { json: any };
}) {
  return (
    <>
      <h1>{title}</h1>
      {text?.json && <RichTextRenderer node={text?.json} />}
    </>
  );
}

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const gqlResponse = await client.request(
    gql`
      query allBlogArticle($slug: String) {
        allBlogArticle(where: { slug: { eq: $slug } }) {
          edges {
            node {
              text {
                json
              }
              title
              slug
              id
            }
          }
        }
      }
    `,
    { slug: params?.slug }
  );

  return {
    props: gqlResponse?.allBlogArticle?.edges?.[0]?.node || {},
  };
};

export async function getStaticPaths() {
  const gqlResponse = await client.request(
    gql`
      query {
        allBlogArticle {
          edges {
            node {
              slug
            }
          }
        }
      }
    `
  );

  const paths: { params: { slug: string } }[] = [];

  gqlResponse?.allBlogArticle?.edges?.forEach(
    (edge: { node: { slug?: string } }) => {
      if (edge?.node?.slug) {
        paths.push({ params: { slug: edge.node.slug } });
      }
    }
  );

  return {
    paths,
    fallback: true,
  };
}

If you navigate to the page, you should now see the headline and full rich text of the blog article displayed on the page.

caisy article preview on blue background darkmode browser

With this, you have everything you need to build a blog page with Caisy and Next.js. For more complex queries and topics, such as preview and localization, further reading may be necessary. For example, in a real-world project, you might want to add pagination to the "getStaticPaths" function in case there are more blog posts that can fit on the first page.