Strapi - Build a blog using Strapi and Next.js

I’m currently running this with strapi deployed to heroku and the frontend to vercel, but I run into the issue where if I create a new article, the article pops up on the frontend, however, upon clicking, I’m met with an error that tells me I can’t find the module, specifically: Error: Cannot find module '/var/task/frontend/.next/server/pages/article/[slug].js'. Are there any solutions for this? I thought getStaticProps would avoid this issue, but I am running into it.

Just wanted to clarify that I fixed my bug; for anyone else who is hosting strapi on heroku and deploying their frontend to vercel, I went into [slug].js for both article and category, and changed fallback in getStaticPaths for both to ‘blocking’.

1 Like

Hi,

On the Seo component it is using the Next.js Head component, and you can put this component anywhere on the page, Next.js, after compiling/bundle, will put the title and other tags on the proper place.

Hi
I managed to run the application at localhost just fine. Now i have deployed Strapi to Heroku and have connected Cloudinary to host the images. All works fine until here.

Finally i want to deploy the frontend to Vercel. But I keep getting this error when I build the frontend

and it poinst to this part of the application .next/server/article/slug.js (It seems I can’t embed more than 1 image here, i can only post that screenshot)

The api in production is identical, i can verify that the url’s with the images are replaced pointing to Cloudinary.

Can someone help me out?
Best regards
Pedro

Hi,
I just end develop my blog using Strapi v4 and Next.js and now I have the first problem. It is possible to deploy Strapi and Next.js (Frontend and Backend) on the same server? For example Hostinger?

I am getting an error on the category page:

on the web page it shows:
## Application error: a client-side exception has occurred (see the browser console for more information).

on the browser console it shows:

GET http://localhost:3000/category/food 404 (Not Found)

can someone help

2 Likes

up, same here

Application error: a client-side exception has occurred (see the browser console for more information).

:face_with_monocle: :eyes:

Works! The only issue I am seeing is this block in the middle of a couple of articles (Shrimp, Story)

<iframe width="560" height="315" src="https://www.youtube.com/embed/PFQGjEJ9PEc" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

Also getting this exact error, did you manage to resolve it?

*It looks like I missed a bit of the tutorial, but we cant have all missed it right? maybe was added a bit later… look now, there is a Category part at the end, do that and it all works.

This does not work if you have more than 25 articles, as the default api response is capped at 24 per api response. This should be updated to do the filter differently I believe.

Here is what I did to help this issue

  1. Use pagination to get a full list of article slugs using pagination instead of the generic /articles route that only returned 24. We have over 200 articles and this worked fine. im sure you could make it more efficient.
// fetch all articles from strapi using pagination, for each
export async function fetchStrapiArticles() {
  const articles = []
  const totalPagesMeta = await fetch(
    `${process.env.NEXT_PUBLIC_STRAPI_API_URL}/api/articles`
  ).then((res) => res.json())
  const totalPages = totalPagesMeta.meta.pagination.pageCount
  for (let i = 1; i <= totalPages; i++) {
    const articlesRes = await fetch(
      `${process.env.NEXT_PUBLIC_STRAPI_API_URL}/api/articles?pagination[page]=${i}`,
      {
        fields: ['slug']
      }
    )
    const json = await articlesRes.json()
    articles.push(...json.data)
  }

  return articles
}

  1. Call this helper method instead to generate all the slugs needed
export async function getStaticPaths() {
  const articleRes = await fetchStrapiArticles()
  const paths = articleRes.map((article) => ({
    params: { slug: article.attributes.slug }
  }))

  return { paths, fallback: false }
}

export async function getStaticProps({ params }) {
  const articlesRes = await fetchStrapi('/articles', {
    populate: ['image', 'author', 'author.image', 'keywords'],
    filters: {
      slug: params.slug
    }
  })

  const categoriesRes = await fetchStrapi('/categories')

  return {
    props: {
      article: articlesRes.data[0],
      categories: categoriesRes
    }
  }
}

@cat - changed node to above. made no difference. :-/

as with Maribell. Same error. followed the tute exactly.

event - compiled client and server successfully in 70 ms (266 modules)
FetchError: request to http://localhost:1337/api/global?populate[favicon]=*&populate[defaultSeo][populate]=* failed, reason: connect ECONNREFUSED 127.0.0.1:1337

type: ‘system’,
errno: ‘ECONNREFUSED’,
code: ‘ECONNREFUSED’

1 Like

I ran into a similar error. I found this on stackoverflow.

This is the modification to the getStrapiURL function in api.js.

export function getStrapiURL(path = "") {
  return `${
    process.env.NEXT_PUBLIC_STRAPI_API_URL || "http://127.0.0.1:1337"
  }${path}`;
}

Cheers!

Anyone know how to sort the articles based on newest updatedAt?

You should be able to use the REST API sort

Also change it in next.config.js

    loader: "default",
    domains: ["127.0.0.1"],

Please note this about the Link next/link <Link>
Add the legacyBehavior prop <Link legacyBehavior><a id="link">Home</a></Link>
Ref: invalid-new-link-with-extra-anchor | Next.js

I had issues using the front end and publishing, unpublishing, or deleting an entry… I suspect the next router is the issue… But I’m not skilled in NextJS. I wrote a webhook to rebuild the next server, and restart the PM2 service. The webhook is setup in the strapi admin settings.

Set up as an express route endpoint…

import { exec } from "child_process"
import { Request, Response } from "express"
import pm2 from "pm2"

const CMD = "yarn build"
const CWD = "/CHANGE/ME"
const PM2_SERVICE_NAME = "CHANGE_ME"

let isBuilding = false

export default class WebhookController {
  // Get all crash reports
  public async blogWebHook(req: Request, res: Response): Promise<Response> {
    const { authorization } = req.headers
    if (!authorization || authorization !== process.env.BLOG_WEBHOOK_SECRET) {
      return res.status(401).json({ message: "Unauthorized" })
    }
    if (isBuilding) {
      // Return Unavailable
      return res.status(503).json({ message: "Unavailable" })
    }
    const { event } = req.body
    if (
      event === "entry.publish" ||
      event === "entry.unpublish" ||
      event === "entry.delete"
    ) {
      // Set the building flag
      isBuilding = true

      // Rebuild the blog's next server
      exec(
        CMD,
        {
          cwd: CWD,
        },
        (error, stdout, stderr) => {
          if (error) {
            console.error(error.message)
            return
          }

          if (stderr) {
            console.error(stderr)
            return
          }

          console.log(stdout)
        }
      ).addListener("exit", (code) => {
        // Unset the building flag
        isBuilding = false

        if (code === 0) {
          // Restart the blog's next server
          pm2.connect((err) => {
            if (err) {
              console.error(err)
              res
                .status(500)
                .json({ message: "PM2 Connection Error", error: err })
            }
            pm2.restart(PM2_SERVICE_NAME, (err, proc) => {
              if (err) {
                console.error(err)
                res
                  .status(500)
                  .json({ message: "PM2 Restart Error", error: err })
              }
              console.log("Next server was restarted")
              pm2.disconnect()
            })
          })
        } else {
          // Return Internal Server Error
          return res
            .status(500)
            .json({ message: "Internal Server Error", error: "Build failed" })
        }
      })
    }
    return res.status(200)
  }
}

hey, my suggestion for any js. queries is react web development company

Hi. Thanks for this tutorial. Here is a possible fix for iframe tag issue. But it will show other HTML elements too.

in /pages/article/[slug].[js|tsx]

import rehypeRaw from "rehype-raw";

// Find this:
<ReactMarkdown children={article.attributes.content} />
// Change to this
<ReactMarkdown children={article.attributes.content} rehypePlugins={[rehypeRaw]}/>

This blog’s post needs an update!
Inside the frontend be sure you have the .npm and you have you git repo initialized and in clean status.
then run

npx @next/codemod new-link .

add & commit

npx @next/codemod next-image-to-legacy-image .

add & commit

npx @next/codemod next-image-experimental .

add & commit again

Hello everyone! :wave:
We updated the tutorial using the new Strapi Starter with Next.js 13, Tailwind, and TypeScript.
Feel free to share your feedback with us!