How to use Image and Preview in your Nextjs & Strapi Blog

Shada - Jul 27 '21 - - Dev Community

Introduction

Strapi and Next.js are the best headless CMS and application development environments available on the market today! They are two powerful technologies that can work in tandem to give you the best digital experience possible.

Strapi is a flexible, open-source Headless CMS that allows developers to use their favorite tools and frameworks while also giving editors power over their content. Next.js is a developer's dream come true with all the features needed for production: hybrid static & server rendering, TypeScript support, smart bundling, assets optimization, and more!

This tutorial will build a blog app using Strapi as the CMS and Next.js as the framework. We will also be using two of its cool features, image optimization and preview mode. Here's an example of what we will be building.

nextjs-strapi-blog

Prerequisites

  • node <=14.0.0
  • npm >=6.0.0
  • yarn
  • Basic knowledge about git
  • The Latest version of Strapi (v3.6.5)
  • The Latest version of Next.js (v.11.0.1)

Goals

This article is all about Next.js and Strapi! You will learn how to use the Image component to add images into your application and use Next.js Preview. See an edited version of your content from Strapi.

Before getting started, let's talk about two features of Next.js that we will use in our blog application.

Optimize image loading with Image

A website's images can significantly impact its loading time. Typically, they are assets that can harm our site performance if they are not in the proper format or size.

Finding and fixing large images is a tedious process. If you don't have an automatic way to do it, you will find yourself spending hours looking for those images that slow your site down and optimize them.

Using the next/image component, we can resize, optimize and serve images in modern formats. This helps us drastically to improve site speed and user experience with images. Next.js can optimize not only locally hosted images, but can work with external data sources as well. In our case, images hosted on Strapi.

Get draft previews with Nextjs preview mode

Static Site Generation is a great way to create static pages ahead of time before users request them. This makes the loading of your blog posts fast, but it makes the editing experience not too pleasant. Each time you edit in a post, and you want to see how the edit looks, and you have to rebuild the whole site.

In this case, preview mode can come to the rescue. Preview mode bypasses static generation and renders the page at the request time instead of build time with the draft data instead of production. In simple words, what it does is making static pages dynamic.

Scaffolding a Strapi Project

Installation

To Install Strapi, you can choose one of the installation methods here.

If you want to follow along with this post, feel free to use https://github.com/amirtds/strapi-sqlite.

    git clone https://github.com/amirtds/strapi-sqlite
    strapi-sqlite
    yarn install && yarn develop
Enter fullscreen mode Exit fullscreen mode

After successfully running the develop command, you should be able to visit the Strapi dashboard at http://localhost:1337/ and create an admin account at http://localhost:1337/admin.

Content Types

We built two content types for our Blog.

  • Author
  • Blog

Below are the images of the fields that should be included in each collection.

Authors

Blogs

Feel free to add new records for each content type by clicking on Authors and Blogs on the left sidebar.

Records

API Access

We are using GraphQL to consume Strapi Data. Make sure your Strapi is set up correctly, and you have the appropriate permissions. Go to settings → Roles → Public and give find and count permission to public.

Permissions

GraphQL Plugin

If you are not using our repo for Strapi, make sure the GraphQL plugin is installed! You can find it in the Marketplace section in the left sidebar.

GraphQL plugin

Building the Frontend with Nextjs

Create New Project

Let's create a new project called next-blog using our example in the GitHub repo and run the development environment. Make sure you created some records in Strapi for Authors and Blogs before running this command.

    npx create-next-app next-blog --example "https://github.com/amirtds/blog/tree/develop"
    cd next-blog
    npm run dev
Enter fullscreen mode Exit fullscreen mode

Now you should be able to access the site at http://localhost:3000.

Nextjs Image
In our blog application, we are using the Nextjs Image component to optimize our images.
For more information, visit https://nextjs.org/docs/basic-features/image-optimization.

Use Image Component

  • To use the image component, you to first import it
    import Image from 'next/image'
Enter fullscreen mode Exit fullscreen mode
  • Set Width, height and src

It's necessary to set the width and height properties of the Image. In our app, We also set the src as src={urlBuilder(post.image[0].url)}

Let's take a deeper look at our code. In the src/components/blogs.jsx we have:

    {siteBlogs.map((post) => (
     <Link key={post.id} href={`/blogs/${post.slug}`}>
        <a>
            <motion.div variants={fadeIn} key={post.id} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}className="flex flex-col rounded-lg shadow-lg overflow-hidden">
            <div className="flex-shrink-0">
                <Image width={600} height={350} className="h-48 w-full object-cover" src={urlBuilder(post.image[0].url)} alt={post.title} />
            </div>
            <div className="flex-1 bg-white p-6 flex flex-col justify-between">
                <div className="flex-1">
                <a href={post.href} className="block mt-2">
                    <p className="text-xl font-semibold text-gray-900">{post.title}</p>
                    <p className="mt-3 text-base text-gray-500">{post.description}</p>
                </a>
                </div>
                <div className="mt-6 flex items-center">
                <div className="flex-shrink-0">
                    <span className="sr-only">{post.author.name}</span>
                    <Image width={50} height={50} className="h-10 w-10 rounded-full" src={urlBuilder(post.author.photo[0].url)} alt={post.title} />
                </div>
                <div className="ml-3">
                    <p className="text-sm font-medium text-gray-900">
                        {post.author.name}
                    </p>
                    <div className="flex space-x-1 text-sm text-gray-500">
                    <time dateTime={post.published}>{post.published}</time>
                    </div>
                </div>
                </div>
            </div>
            </motion.div>
        </a>
     </Link>
    ))}
Enter fullscreen mode Exit fullscreen mode

*siteBlogs* is an array that has a list of all of our blogs. We are looping over it and creating a blog card based on each blog item in this list. In the Image src={urlBuilder(post.image[0].url)} result will be STRAPI_URL/IMAGE_URL for example http://localhost:1337/uploads/strapi_cover_1fabc982ce_1c5a5b390a.png.

Set domain in next.config.js. In this file, you should have something like

    module.exports = {
        images: {
          domains: ["localhost"],
        },
      }
Enter fullscreen mode Exit fullscreen mode

In our case, we have

    module.exports = {
        images: {
          domains: [configs.STRAPI_DOMAIN],
        },
      }
Enter fullscreen mode Exit fullscreen mode

Which configs.STRAPI_DOMAIN is what we have in the configs.json file for the Strapi domain.
We do not have many pictures in our Blog, but after using the image component, we got great results from the lighthouse audit.

lighjouse report

Nextjs Preview

Preview makes a pre-rendered page to be visible as server-side rendered pages. This means that, with Preview, you can see your changes live without having to go through the whole build process again!

How it works ?

NextJS checks your site cookies, and if two special cookies are present, it considers the request as preview mode, and it bypasses the SSG. For more information about Preview, please visit https://nextjs.org/docs/advanced-features/preview-mode.

Create APIs

We need to create 2 APIs for preview functionality.

Firstly, we will have the /api/preview, which adds preview mode cookies to your site. After successfully implementing this API, calls to it will add __prerender_bypass and __next_preview_data cookies.

cookies

Open the preview.js file and add the following codes:

    // src/pages/api/preview.js

    import { getPost } from 'lib/api'

    export default async function handler(req, res) {
    # Check if the user is requesting with valid token
     if (req.query.secret !== (process.env.STRAPI_PREVIEW_SECRET)) {
       return res.status(401).json({ message: "Invalid token" });
     }

    # Make sure the blog post actiually exits
     const slug = req.query.slug
     const blogData = await getPost(slug)
     if (!blogData) {
       return res.status(401).json({ message: "Invalid slug" });
     }
    # If all good we set preview cookies
    # And we redirect the user to the preview version of the blog post
     res.setPreviewData({});

     res.writeHead(307, { Location: `/blogs/${slug}` });
     res.end();
    };
Enter fullscreen mode Exit fullscreen mode

Secondly, we will create the last API /api/exit-preview. To return to SSG mode, we need to remove those cookies from our browser. This API will take care of that.

    // src/pages/api/exit-preview.js

    export default async function exit(_, res) {
        // Exit the current user from "Preview Mode". This function accepts no args.
        res.clearPreviewData()
        // Redirect the user back to the index page.
        res.writeHead(307, { Location: "/" })
        res.end()
      }
Enter fullscreen mode Exit fullscreen mode

Fetch live or preview content from Strapi

The last step is to fetch data from Strapi based on preview mode. Before we start fetching and displaying the data from our Strapi, let's look at how to detect preview mode.

The following context object has a preview attribute that returns true or false
How we use it on our page. In the getStaticProps function of your page, you can use context as an argument, and based on the status of Preview, we fetch live or preview content from Strapi.

    // src/pages/blogs/[slug].js

    export const getStaticProps = async (context) => {
        const previewMode = context.preview == false || context.preview == null ? "live" : "preview"
        const slug = context.params.slug
        const BLOG_QUERY = gql`
        query($slug: String, $previewMode: String){
          blogs(where: {slug: $slug, _publicationState: $previewMode}){
            id
            title
            subtitle
            description
            published
            slug
            image{
              url
            }
            author {
              name
              photo {
                url
              }
            }
            content
          }
        }
        `
        const { data:blogData } = await apolloClient.query({
          query: BLOG_QUERY,
          variables: {
            slug,
            previewMode
          },
          preview: context.preview,
        })
Enter fullscreen mode Exit fullscreen mode

As you see, we have _publicationState condition in our call that can be live or Preview.

How it looks

We changed "Build a Next.js Blog with Strapi and use preview and image component!" Blog title to "Build a Next.js Blog with Strapi - Draft," but I didn't build the site again, let's see how it looks like.

Conclusion

In this article, we learned how to leverage the power of Next.js previews and images optimization with Strapi content.

We hope this tutorial has helped teach you how easy it is to integrate these tools into your application. It is more important than ever to create an exceptional digital experience for your customers in today's world.

The Blog is hosted at Vercel: https://nextjs-blog-rose-eta.vercel.app
You can find the source code at https://github.com/amirtds/blog

With Strapi and Next.js, you can do just that! We saw how these two powerful technologies work together seamlessly to help you build a blog app with previews and optimized images quickly.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .