On-demand ISR not working

Hi guys,

I am trying to implement on-demand incremental static regeneration into my site.
I have followed the article here.

I created a PRISMIC_WEBHOOK_SECRET in my env.local as well as on my Netlify account.

I made a revalidate.js in my api folder with the code below:

// pages/api/revalidate.js

import * as prismic from '@prismicio/client';
import * as prismicH from '@prismicio/helpers';

// Import your app's Link Resolver (if your app uses one)
import { linkResolver, repositoryName } from '../../prismicio';

/**
 * This API endpoint will be called by a Prismic webhook. The webhook
 * will send an object containing a list of added, updated, or deleted
 * documents. Pages for those documents will be rebuilt.
 *
 * The Prismic webhook must send the correct secret.
 */
export default async function handler(req, res) {
	if (req.body.type === 'api-update' && req.body.documents.length > 0) {
		// Check for secret to confirm this is a valid request
		if (req.body.secret !== process.env.PRISMIC_WEBHOOK_SECRET) {
			return res.status(401).json({ message: 'Invalid token' });
		}

		// If you have a `createClient()` function defined elsewhere in
		// your app, use that instead
		const client = prismic.createClient(repositoryName);

		// Get a list of URLs for any new, updated, or deleted documents
		const documents = await client.getAllByIDs(req.body.documents);
		const urls = documents.map(doc => prismicH.asLink(doc, linkResolver));

		try {
			// Revalidate the URLs for those documents
			await Promise.all(
				urls.map(async url => await res.unstable_revalidate(url))
			);

			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');
		}
	}

	// If the request's body is unknown, tell the requester
	return res.status(400).json({ message: 'Invalid body' });
}

I then went into my Prismic settings and created a webhook that pointed to the API endpoint with my secret.

It is triggering and I am getting a 200 response when I update one of my pages but the page does not update.

Any ideas what I am doing wrong?

1 Like

Hello @rsheppard83

Thanks for reaching out to us.

I can not tell you why it's not working. Have you created the PRISMIC_WEBHOOK_SECRET and entered the correct value in the Prismic webhooks?

Thanks,
Priyanka

1 Like

Yes added and working but not updating pages.

Hi @rsheppard83,

Setting up the revalidate.js file might require some customization depending on your project. Here are a few things to check for:

  • Do you have a custom Prismic createClient() function defined elsewhere in your app? This might be done to provide an access token or Route Resolver config.

    • If yes, replace the const client = prismic.createClient() call in the middle of the handler's code.
    • If no, ensure you are creating the client correctly. If your Prismic repository is protected by an access token, the token should be provided to the createClient() call.
  • Does your app use a Link Resolver or Route Resolver?

    • Link Resolver: Be sure it is being imported and passed to the prismicH.asLink() function correctly.
    • Route Resolver: Be sure it is set in the client via the routes option. See the createCilent() bullet point above.
  • You can debug the endpoint by logging the URLs that are being revalidated. The simplest way to do this is by logging the urls constant and checking your API endpoint logs. You should see a list of URLs associated with the documents you published. Verify that it is the correct URL.

    • Check for trailing slashes. The URL should be identical to your app's set up. If Next.js is configured to not use trailing slashes, be sure that the logged URL does not include a trailing slash. (This can be changed in your Link Resolver or Route Resolver)

Hopefully this helps. If you are still unable to get on-demand ISR working after checking these points, let us know and we can help further.

Thanks!

@angeloashmore Just wanted to chip in to this discussion. I decided to try out on-demand ISR for my own website and I run into issues as well. I tested via console.logs but the function didn't go as far as the url variable. There is something wrong with the req params but I can't figure out why.

The output of

console.log(req.body)
  console.log(req.query)
  console.log(req.body.type)
  console.log(req.body.documents)

is

{}
undefined
undefined

after I published a document on Prismic.

And this is the console.log when only outputting req

_maxListeners: undefined,
      _connections: 1,
      _handle: [TCP],
      _usingWorkers: false,
      _workers: [],
      _unref: false,
      allowHalfOpen: true,
      pauseOnConnect: false,
      httpAllowHalfOpen: false,
      timeout: 0,
      keepAliveTimeout: 5000,
      maxHeadersCount: null,
      headersTimeout: 60000,
      requestTimeout: 0,
      _connectionKey: '4:127.0.0.1:0',
      [Symbol(IncomingMessage)]: [Function: IncomingMessage],
      [Symbol(ServerResponse)]: [Function: ServerResponse],
      [Symbol(kCapture)]: false,
      [Symbol(async_id_symbol)]: 9
    },
    parser: HTTPParser {
      '0': [Function: bound setRequestTimeout],
      '1': [Function: parserOnHeaders],
      '2': [Function: parserOnHeadersComplete],
      '3': [Function: parserOnBody],
      '4': [Function: parserOnMessageComplete],
      '5': [Function: bound onParserExecute],
      '6': [Function: bound onParserTimeout],
      _headers: [],
      _url: '',
      socket: [Circular *1],
      incoming: [Circular *2],
      outgoing: null,
      maxHeaderPairs: 2000,
      _consumed: true,
      onIncoming: [Function: bound parserOnIncoming],
      [Symbol(resource_symbol)]: [HTTPServerAsyncResource]
    },
    on: [Function: socketListenerWrap],
    addListener: [Function: socketListenerWrap],
    prependListener: [Function: socketListenerWrap],
    _paused: false,
    _httpMessage: ServerResponse {
      _events: [Object: null prototype],
      _eventsCount: 1,
      _maxListeners: undefined,
      outputData: [],
      outputSize: 0,
      writable: true,
      destroyed: false,
      _last: false,
      chunkedEncoding: false,
      shouldKeepAlive: false,
      _defaultKeepAlive: true,
      useChunkedEncodingByDefault: true,
      sendDate: true,
      _removedConnection: false,
      _removedContLen: false,
      _removedTE: false,
      _contentLength: null,
      _hasBody: true,
      _trailer: '',
      finished: false,
      _headerSent: false,
      socket: [Circular *1],
      _header: null,
      _keepAliveTimeout: 5000,
      _onPendingData: [Function: bound updateOutgoingData],
      _sent100: false,
      _expect_continue: false,
      statusCode: 200,
      write: [Function (anonymous)],
      end: [Function (anonymous)],
      status: [Function (anonymous)],
      send: [Function (anonymous)],
      json: [Function (anonymous)],
      redirect: [Function (anonymous)],
      setPreviewData: [Function (anonymous)],
      clearPreviewData: [Function (anonymous)],
      unstable_revalidate: [Function (anonymous)],
      [Symbol(kCapture)]: false,
      [Symbol(kNeedDrain)]: false,
      [Symbol(corked)]: 0,
      [Symbol(kOutHeaders)]: null
    },
    [Symbol(async_id_symbol)]: 35,
    [Symbol(kHandle)]: TCP {
      reading: true,
      onconnection: null,
      _consumed: true,
      [Symbol(owner_symbol)]: [Circular *1]
    },
    [Symbol(kSetNoDelay)]: false,
    [Symbol(lastWriteQueueSize)]: 0,
    [Symbol(timeout)]: null,
    [Symbol(kBuffer)]: null,
    [Symbol(kBufferCb)]: null,
    [Symbol(kBufferGen)]: null,
    [Symbol(kCapture)]: false,
    [Symbol(kBytesRead)]: 0,
    [Symbol(kBytesWritten)]: 0,
    [Symbol(RequestTimeout)]: undefined
  },
  _consuming: false,
  _dumped: false,
  cookies: [Getter/Setter],
  query: {},
  previewData: [Getter/Setter],
  preview: [Getter/Setter],
  body: '',
  [Symbol(kCapture)]: false,
  [Symbol(RequestTimeout)]: undefined,
  [Symbol(NextRequestMeta)]: {
    __NEXT_INIT_URL: '/api/revalidate/',
    __NEXT_INIT_QUERY: {},
    __nextHadTrailingSlash: undefined
  }
}

I just did a quick test to reproduce the same empty req.body situation as @kris. It looks like a trailing slash in the webhook's URL breaks the endpoint.

  • :white_check_mark: Good: https://example.com/api/revalidate
  • :x: Bad: https://example.com/api/revalidate/

With a trailing slash, req.body is empty. Without the trailing slash, req.body contains the correct webhook payload.

I confirmed this behavior on both Vercel and Netlify.

I'm not sure if this is a Prismic or Next.js limitation, but I'll check with the Prismic team to see if it's something on our side.

Thanks for the heads up, and hopefully that change allows you to get working on-demand ISR. :slightly_smiling_face:

Thanks for investigating @angeloashmore. I actually had this endpoint at the start, but it still didn't work. The reason being is having that setting in next.config.js:

trailingSlash: true

This means that even if the endpoint is /revalidate, next.js will redirect it to /revalidate/ before processing the endpoint.

Unfortunately, I don't think I can use on-demand ISR with the trailing slash enabled until the Prismic team has fixed this issue.

Potentially helpful discussion on the next.js forum - Nextjs should have more options for trailing slash. · Discussion #23988 · vercel/next.js · GitHub

1 Like

I've used createClient and used postman to test my body and it is returning the correct slugs in this format:

url: [
"/about"
]

Still doesn't work. My api endpoints do not include any trailing slashes.

hi! is there any news about ISR not working when triggering the webhook in prismic in production (it works just fine locally)?

I triggered the change from Postman to prod (with body taken from webhook) and it works. But when I try to do it with prismic it doesn't work.