6

I have a little problem with the ISR. I have the revalidate prop equal 1s like here

export async function getStaticProps({ params }) {
  const data = await client.getEntries({
     content_type: "product",
    "fields.name": params.slug,
  });


  if (!data.items[0]) {
    return {
      notFound: true,
    };
  }
  return {
    props: {
      article: data.items[0],
      revalidate: 1,
    },
  };
}

When I create product in Contentful, the page is created as I expected. When I want to entry to page that doesn't exist I get 404 error as expected. Problem starts when I change something in Contentful in existing product or I delete it.

When I delete the product in Contentful, the list of products in the products page is updated and the product disappears but I can still entry in page of that product. Also when I rename the product name the list of products is updated but I still can entry to the earlier page name.

Is there any solution to solve this problem?

getStaticPaths

export async function getStaticPaths() {
  const data = await client.getEntries({
    content_type: "product",
  });

  return {
    paths: data.items.map((item) => ({
      params: { slug: item.fields.name },
    })),
   fallback: true,
 };
}

Product page

const Article = ({ article }) => {
  const router = useRouter();

  if (router.isFallback) return <p>Loading...</p>;

  return (
    <div>
      <h1>Hello! {article.fields.name}</h1>
      <Link href="/about">
        <a>Back to about!</a>
      </Link>
    </div>
  );
};

EDIT When I change product name from "product77" to "product7" in Contentful after revalidate static page in my build for product77 still exist and I still can entry to that page. enter image description here

Shouldn't it be removed after revalidation?

juliomalves
  • 42,130
  • 20
  • 150
  • 146
David
  • 73
  • 1
  • 6
  • Hey! Lee from Vercel here. We're working on a solution for this. Feel free to email lee at vercel.com if you would like to try it out. – leerob Apr 09 '21 at 22:15
  • Hello, did you find a solution yet? I have a similar problem: https://stackoverflow.com/questions/67864802/next-js-isr-page-not-being-deleted-after-deleting-it-in-cms – Mark Jun 08 '21 at 06:06
  • 1
    I am also having similar problem and until now can't find the right solution. I think the team still work on it since ISR is pretty new – krehwell Jun 25 '21 at 01:52
  • I got to research about Next.js to make sure that the frameworks meet our product scenarios. And now I have arrived here at this same problem. Hope we hear some solutions soon on this. – Sagar Saini Nov 28 '21 at 10:46
  • You may want to look into [ISR on-demand revalidation](https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration#on-demand-revalidation-beta) to update (or in your case delete) the cached page. – juliomalves May 30 '22 at 12:34
  • @leerob I am also running the same problem. generating product pages as per product name, now product name can change/rename but product id is same by which we are fetching data, Now I have two pages with same info and we have old product page which can be access directly or from bookmark url. – Rakesh Kumar Jul 23 '22 at 15:18

1 Answers1

4

Removed pages can't be deleted, but served with a 404 instead. One has to use

The below example uses entry id's to build the paths, e.g. example.com/post/90803230238. For non-id path's, e.g. with a custom field example.com/post/how-to-make-icecream, please read the note at the end of this answer.

A pre-requisite for this is that getStaticPaths and getStaticProps are configured accordingly.

// /pages/blog/[...post].jsx

function Post({ postData }) {
    return (
        // ...
    );
}

// Only runs once during build
export async function getStaticPaths() {
    const res = await fetch("https://.../posts");
    const posts = await res.json();

    // Prerender paths during build
    const paths = posts.map((post) => ({
        params: { post: post.id },
    }));

    return { 
        paths, 
        fallback: "blocking" // must be "blocking"
    };
}

// Runs during build and every revalidation
export async function getStaticProps(context) {
    const res = await fetch(`https://.../posts/${context.params.post}`);
    const postData = await res.json();

    // Check if the page exists in the CMS using the API response
    if(!postData){
        // The page doesn't exist in the CMS, return notFound to trigger a 404
        return{
            notFound: true,
            // Pick one of the following
            revalidate: 30, // <- ISR, interval in seconds between revalidations
            revalidate: false // On-demand
        }
    }

    // Return page props
    return {
        props: {
            postData,
        },
        // Pick one of the following
        revalidate: 30, // <- ISR, interval in seconds between revalidations
        revalidate: false // On-demand
    };
}

The main takeaway from the above code is:

  • You can pre-render paths in getStaticPaths which only runs during the initial build
  • getStaticPaths must use "blocking" as the fallback value
  • getStaticProps which runs during the initial build and every revalidation, should ask with an API request each time for the page status (Published, Unpublished, ...) - If the page is active, return the page props - If the page is inactive/deleted, return a notFound:true, falling back to the ./pages/404.jsx page

On-demand revalidation needs an additional file, the API endpoint where the webhook can send notifications to.

// /pages/api/revalidate-post.js

export async function handler(req, res) {
    try {
        const reqBody = JSON.parse(req.body);
        await res.revalidate(`/post/${reqBody.sys.id}`); // revalidate post using entry id
        return res.json({ revalidated: true });
    } catch (err) {
        return res.status(500).send("Error revalidating");
    }
}

The res.revalidate(``/${reqBody.path}``) invokes a fresh evaluation of the page using the logic from getStaticProps.

Now, if somebody would delete the page in the CMS, and trigger the above revalidation webhook handler for the deleted page path, then the path serves a 404 page instead of the original page.

The page itself, however, is not deleted from the disk.


Revalidating using non-id, custom field values for paths

In Contentful, during an "Unpublish" webhook notification, only the unpublished entry ID and a type: "DeletedEntry" are present in the webhook response body. All other field values, e.g. the path required to trigger the revalidation, are not available. Here, we have to make an additional request to fetch the inactive's post field values.

Contentful provides the Content Preview API, which allows you to fetch data even from inactive entries.

Heres the pseudocode for the webhook API endpoint, when using custom path values other than the ID:

export async function handler(req, res) {
    try {
        const reqBody = JSON.parse(req.body);

        let pageToRevalidate = "";
        if (reqBody.sys.type === "DeletedEntry") {
            /**
             * "Unpublished" (type = DeletedEntry) status entries in
             * Contentful are not available on the normal API endpoints. Here, we
             * have to make an additional API request to the Preview API service,
             * asking for data for the entry with id reqBody.sys.id.
             */
            const unpublishedRes = await fetch(
                `https://previewAPI.../posts/${context.params.post}`
            );
            const unpublishedData = await unpublishedRes.json();
            pageToRevalidate = `/${unpublishedData.fields.slug}`;
        } else {
            pageToRevalidate = `/${reqBody.fields.slug}`;
        }
        await res.revalidate(pageToRevalidate);
        return res.json({ revalidated: true });
    } catch (err) {
        return res.status(500).send("Error revalidating");
    }
}
Advena
  • 1,664
  • 2
  • 24
  • 45
  • I'd like to add a bit of a warning to anyone struggling like we did with this scenario. What was written here is 100% correct. However, it does not take into account that if/when the memory of nextjs is exhausted (this happens to us due to high page count), then it will forget that certain routes revalidated to 404/redirects. In this case your CMS might have deleted the page BUT it's still visible online and client gets VERY unhappy. Our only solution was to "manually" delete files from the `./next/server/pages` folder whenever a page evaulated to a 404 or redirect! – Tackle Mar 09 '23 at 16:39
  • @Tackle, out of curiosity, can you elaborate on the high page count exhausting NextJS memory? – Advena Mar 10 '23 at 07:45
  • Not sure where our limit is, but 2000 pages is far too many. isrMemoryCacheSize is defaulted to 50MB so whenever this runs out the problem arises. You can test this by setting it to a very low value. Not sure if disabling it by setting to 0 introduces/changes any behavior stopping you from testing it. – Tackle Mar 11 '23 at 09:30
  • I'm interested myself to have deleted pages also deleted on disk. Would you be able to open a Next.JS issue and share the link here? – Advena Mar 13 '23 at 09:07
  • Yes, here you go! https://stackoverflow.com/questions/75731978/how-can-you-delete-statically-generated-nextjs-files-on-404-and-redirects/75731979#75731979 @Tanckom – Tackle Mar 14 '23 at 10:31
  • I was talking about opening an issue on Next.JS GitHub repository... Not creating a question that you immediately know the answer for, which I even believe is against StackOverflow's TOS. – Advena Mar 14 '23 at 11:38
  • No, that's definitely allowed on SO, they even have streamlined functionality for it when creating an issue, it's called "Answer your own question Q & A style". So you don't have to worry about that. Sorry for misunderstanding your request and trying to be helpful, I read your message a bit too fast. You can go and create an issue yourself, I see no reason for me to do that: this is your request. I don't think the developers really care as similar functionality has been discussed years ago. – Tackle Mar 14 '23 at 12:29
  • Oh, my bad then. It's been some time i used that feature. – Advena Mar 14 '23 at 14:05