8

I think this is quite a vanilla question but I can't find anything on Google.

I'm learning NextJs (using TypeScript) and I've got a site successfully working with dynamic routes, SSR, and incremental regeneration all setup and deployed to Vercel. Here's an example of the GetStaticProps and GetStaticPaths code in my dynamic route handler:

export const getStaticPaths: GetStaticPaths = async () => {
    const routes = new CmsHelper().GetRoutes();

    const paths = (await routes).items.map((item, index, items) => {
        return item.fields.urlPath;
    })

    return {
        paths: paths,
        fallback: 'blocking',
    };
}

export const getStaticProps: GetStaticProps = async (context) => {
    const urlParts = context.params?.url as string[] || [];
    const urlPath = `/${urlParts.join('/')}`;
    const article = await new CmsHelper().GetArticle(urlPath);
    return {
        props: {
            article
        },
        revalidate: 10,
    }
}

If I request a new path that was published in the cms after build time, it successfully regenerates on the server and returns the page.

So far so good.

However, if I unpublish a route in my CMS... it is still returned by the app, unless I rebuild and redeploy (or otherwise cause the process to restart, in dev).

So my question is: how do I dynamically cause NextJs to remove a dynamic route from its GetStaticPaths cache?

I understand that GetStaticProps is going to be called at most once every 10 seconds because of the revalidate configuration. But as far as I know, GetStaticPaths will only be called if a request comes in from a route that is not currently cached(?)

In other words, for an integration with a headless CMS, how can I support unpublishing or renaming pages with NextJs without triggering a rebuild/deploy?

Thanks in advance!

Alex Norcliffe
  • 2,439
  • 2
  • 17
  • 21

3 Answers3

1

Maybe try this approach described here.

Also providing the code here for reference purposes.

// pages/blog/[slug].js

import {useRouter} from 'next/router'
import DefaultErrorPage from 'next/error'

export async function getStaticProps({ params }) {
  // fetch data from CMS through params
  const post = await getBlogItem(params.slug)
  return {
    props: {
      post
    }
  }
}

export async function getStaticPaths() {
  return {
    fallback: true,
    paths: []
  }
}

export default function MyPage({post}) {
  const router = useRouter()

  if(router.isFallback) {
     return <h1>Loading...</h1>
  }

  // This includes setting the noindex header because static files always return a status 200 but the rendered not found page page should obviously not be indexed
  if(!post) {
    return <>
      <Head>
        <meta name="robots" content="noindex">
      </Head>
      <DefaultErrorPage statusCode={404} />
    </>
  }

  return <h1>{post.title}</h1>
}

So, instead of deleting/clearing the cache, maybe try to handle successfully that the content you are fetching is "unpublished" and if so, you can display a 404 Not found page or something of your choice.

Something like this:

export async function getStaticProps(context) {

    const {params: {id}} = context

    let data;
    try {
        data = await httpClient...
    } catch (err) {
        if (not err is caused due to content being unpublished){
            // re throw it
            throw err;
        }
        // Else handle it gracefully
        data = null;
    }

    return {
        props: {
            data,
        },
        revalidate: 1
    };
}

and then on your View:

export default function Data(props) {
    const {data} = props;

    const router = useRouter()

    // If the page is not yet generated, this will be displayed
    // initially until getStaticProps() finishes running
    if (router.isFallback) {
        return <div>Loading...</div>
    }

    if (!data) {
        return (
            <>
                <Head>
                    <meta name="robots" content="noindex"/>
                </Head>
                <DefaultErrorPage statusCode={404}/>
            </>
        )
    }

    return <DataView {...data} />;

}
entropyfeverone
  • 1,052
  • 2
  • 8
  • 20
0

I was facing a similar but different problem: Deploying my project generated URL from the data, but I couldn't add any new URLs when the data in the DB changed.

The key to my problem was that I was setting fallback to false in my getStaticPaths method. When I set it to true the production deploy started behaving like my local development build.

I see that you're passing a string value for your fallback property. You might try using a boolean value as described here: https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required

Dharman
  • 30,962
  • 25
  • 85
  • 135
aproximation
  • 491
  • 4
  • 13
  • Actually: check out [revalidate: 1](https://nextjs.org/docs/basic-features/data-fetching#incremental-static-regeneration) as returned by getStaticProps. - fallback: true fixes new URLs - revalidate: 1 fixes old URLs – aproximation Mar 03 '21 at 04:58
-1

With Next.js 12.2 released in summer 2022 you can use On-Demand Incremental Static Regeneration to update generated pages on demand, i.e. when you publish changes in your headless CMS and use its webhook features.

// pages/api/revalidate.js

export default async function handler(req, res) {
  // Check for secret to confirm this is a valid request
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: 'Invalid token' })
  }

  try {
    // this should be the actual path not a rewritten path
    // e.g. for "/blog/[slug]" this should be "/blog/post-1"
    await res.revalidate('/path-to-revalidate')
    return res.json({ revalidated: true })
  } catch (err) {
    // If there was an error, Next.js will continue
    // to show the last successfully generated page
    return res.status(500).send('Error revalidating')
  }
}

https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration#using-on-demand-revalidation

Oskar Duveborn
  • 2,189
  • 16
  • 20