serverless-next.js icon indicating copy to clipboard operation
serverless-next.js copied to clipboard

Support on-demand purge nextjs cache released in 12.1

Open davidh99720 opened this issue 3 years ago • 17 comments

Is your feature request related to a problem? Please describe. Without the mechanism to purge the nextjs cache, it is a pitfall in production use and causing a lot of problems.

Describe the solution you'd like Support the res.unstable_revalidate in api which can be controlled by backend to purge the cache and regenerate the page requested. It is supported in Nextjs 12.1, and it will be great if we can support it as well: https://nextjs.org/blog/next-12-1

Describe alternatives you've considered Disable the next.js cache totally. To be tested. Using CDN Cache instead with a purge api call.

Additional context An example is in this video: https://www.youtube.com/watch?v=BGexHR1tuOA&t=16s

davidh99720 avatar Mar 30 '22 22:03 davidh99720

I am trying this function on my website (using next 12.1, sls-nextjs-component) and it return error 500. Can you confirm that it is not currently supported? ...and that I am not the problem? Hope this feature will be implemented soon. It would be very useful and I could remove the revalidate timer.

m0rph3u5-zip avatar Mar 31 '22 13:03 m0rph3u5-zip

This is the error I get when I tried to call res.unstable_revalidate

Error: Failed to revalidate /: Invariant: required internal revalidate method not passed to api-utils

jlaramie avatar Apr 05 '22 17:04 jlaramie

same response here!

m0rph3u5-zip avatar Apr 06 '22 10:04 m0rph3u5-zip

would be nice to have it? what other ways to consider? maybe calling revalidation lambda manually?

kigorw avatar Apr 23 '22 18:04 kigorw

Very new to Serverless Next.js but it seems like a lot of the groundwork for this has been completed in https://github.com/serverless-nextjs/serverless-next.js/pull/1028. So perhaps all that's required is another Lambda function that can be called with a secret token along with a path, which would then trigger a message in the SQS queue for the Next.js Regeneration Lambda to handle. (No doubt easier said than done!)

Or seeing as our use-case for this would be on-demand from a CMS, we could manufacturer a SQS message from that CMS, although it seems like the Regenerate Lambda is expecting a CloudFront request object, which is not ideal to have to manufacture!

mtwalsh avatar May 04 '22 15:05 mtwalsh

I was able to trigger regeneration. Feel free to adapt this code to your needs until we get some better official approach.

import { triggerStaticRegeneration } from '@sls-next/lambda-at-edge/dist/lib/triggerStaticRegeneration'
import { defaultRegion, siteSQS, websiteBucket } from '../../config'
import * as s3 from './s3'

export async function getBuildId(): Promise<string> {
  const x = await s3.get('BUILD_ID', websiteBucket)

  return (x || '').toString()
}

export function getPageKey(buildId: string, key: string) {
  return `${getStaticPagesPath(buildId)}/${key}.html`
}
export function getStaticPagesPath(buildId: string) {
  return `static-pages/${buildId}`
}

export function getRegenerationOptions({
  queueName,
  buildId,
  page,
  lang,
  bucket,
  region,
  counter
}: {
  queueName: string
  buildId: string
  page: string
  lang: 'en' | 'ka'
  bucket: string
  region: string
  counter: number
}) {
  return {
    basePath: '',
    pagePath: 'pages/' + (page || 'index') + '.js',

    pageS3Path: '/' + lang + (page ? '/' + page : '') + '.html',
    storeName: bucket,
    storeRegion: region,
    request: {
      uri: '/' + lang + (page ? '/' + page : ''),
      origin: {
        s3: {
          region,
          domainName: `${bucket}.s3.${region}.amazonaws.com`,
          path: '/' + getStaticPagesPath(buildId)
        }
      }
    } as AWSLambda.CloudFrontRequest,
    eTag: Date.now() - 1000 + counter,
    lastModified: Date.now() - 1000 + counter,
    queueName
  }
}

export async function regeneratePage(lang: 'en' | 'ka', page: string, counter = 0) {
  const buildId = await getBuildId()
  console.log('queue', siteSQS)

  const options = getRegenerationOptions({
    queueName: siteSQS,
    buildId,
    lang,
    page,
    bucket: websiteBucket,
    region: defaultRegion,
    counter
  })

  console.log(options)

  const x = await triggerStaticRegeneration(options as any)

  console.log('event', x)
}


kigorw avatar May 04 '22 15:05 kigorw

Wow, thanks for the quick response @kigorw.

mtwalsh avatar May 04 '22 15:05 mtwalsh

I was able to trigger regeneration. Feel free to adapt this code to your needs until we get some better official approach.

import { triggerStaticRegeneration } from '@sls-next/lambda-at-edge/dist/lib/triggerStaticRegeneration'
import { defaultRegion, siteSQS, websiteBucket } from '../../config'
import * as s3 from './s3'

export async function getBuildId(): Promise<string> {
  const x = await s3.get('BUILD_ID', websiteBucket)

  return (x || '').toString()
}

export function getPageKey(buildId: string, key: string) {
  return `${getStaticPagesPath(buildId)}/${key}.html`
}
export function getStaticPagesPath(buildId: string) {
  return `static-pages/${buildId}`
}

export function getRegenerationOptions({
  queueName,
  buildId,
  page,
  lang,
  bucket,
  region,
  counter
}: {
  queueName: string
  buildId: string
  page: string
  lang: 'en' | 'ka'
  bucket: string
  region: string
  counter: number
}) {
  return {
    basePath: '',
    pagePath: 'pages/' + (page || 'index') + '.js',

    pageS3Path: '/' + lang + (page ? '/' + page : '') + '.html',
    storeName: bucket,
    storeRegion: region,
    request: {
      uri: '/' + lang + (page ? '/' + page : ''),
      origin: {
        s3: {
          region,
          domainName: `${bucket}.s3.${region}.amazonaws.com`,
          path: '/' + getStaticPagesPath(buildId)
        }
      }
    } as AWSLambda.CloudFrontRequest,
    eTag: Date.now() - 1000 + counter,
    lastModified: Date.now() - 1000 + counter,
    queueName
  }
}

export async function regeneratePage(lang: 'en' | 'ka', page: string, counter = 0) {
  const buildId = await getBuildId()
  console.log('queue', siteSQS)

  const options = getRegenerationOptions({
    queueName: siteSQS,
    buildId,
    lang,
    page,
    bucket: websiteBucket,
    region: defaultRegion,
    counter
  })

  console.log(options)

  const x = await triggerStaticRegeneration(options as any)

  console.log('event', x)
}

Hi, thank you so much for having shared this code. I wanted to try to understand your approach. For example, I have a cms to manage data. Stack: Angular, Apigw lambda in nodejs. Is your approach frontend in nextjs api or is it a call you make in your backend? It would be useful to see an example of parameters for this object: { queueName: siteSQS, buildId, lang, page, bucket: websiteBucket, region: defaultRegion, counter }

This is my prototype: https://d2flbk08lhbnjs.cloudfront.net I apologize for the question and if you can it seems so banal. Thanks!

m0rph3u5-zip avatar May 11 '22 08:05 m0rph3u5-zip

@kigorw hi, could you explain more about the implementation of your code? Thanks!

RodrigoTomeES avatar May 31 '22 18:05 RodrigoTomeES

@m0rph3u5-zip I make a call from my separate serverless lambda function.

@RodrigoTomeES I reused triggerStaticRegeneration function that sends SQS message. The main challenge was to configure it with right parameters.

kigorw avatar Jun 01 '22 07:06 kigorw

@kigorw Hi thanks, and how I can deploy the lambda to work with serverless-next? I mean, once it's deployed, I'll have to call it every time, for example, a product is saved in Wordpress, right? But how I can deploy the lambda in AWS to work with the serverless-next bucket?

RodrigoTomeES avatar Jun 02 '22 09:06 RodrigoTomeES

Hello guys, I'm going through this deployment problem on aws My project has ISR but whenever I deploy to AWS it returns this error "Error: Failed to revalidate /: Invariant: required internal revalidate method not passed to api-utils" Do I have to configure something on the AWS side? Because I deployed to Versel and it worked

rafaelone avatar Jun 27 '22 14:06 rafaelone

Still getting the same error: "Invariant: required internal revalidate method not passed to api-utils" when trying to call await res.revalidate(...) from next.js api (output from API Lambda@Edge). Going to try to run the code above in new lambda function

sitezen avatar Sep 12 '22 17:09 sitezen

@sitezen any luck? I'm facing the same issue and have not found any solution.

AlejandroJAguilarP avatar Sep 13 '22 19:09 AlejandroJAguilarP

@AlejandroJAguilarP I'd like to have Lambda function which will trigger on DynamoDB events and will run revalidation of specified page. Code above requires to add too huge package to Lambda function to call of triggerStaticRegeneration() (or maybe I doing something wrong). So, as a temporary solution, I'll try to send SQS message even with some hardcoded params, captured from SQS queue (as easiest way). While it not done, I've (temporary) added revalidate into getStaticProps

sitezen avatar Sep 14 '22 10:09 sitezen

I was able to trigger regeneration. Feel free to adapt this code to your needs until we get some better official approach.

import { triggerStaticRegeneration } from '@sls-next/lambda-at-edge/dist/lib/triggerStaticRegeneration'
import { defaultRegion, siteSQS, websiteBucket } from '../../config'
import * as s3 from './s3'

export async function getBuildId(): Promise<string> {
  const x = await s3.get('BUILD_ID', websiteBucket)

  return (x || '').toString()
}

export function getPageKey(buildId: string, key: string) {
  return `${getStaticPagesPath(buildId)}/${key}.html`
}
export function getStaticPagesPath(buildId: string) {
  return `static-pages/${buildId}`
}

export function getRegenerationOptions({
  queueName,
  buildId,
  page,
  lang,
  bucket,
  region,
  counter
}: {
  queueName: string
  buildId: string
  page: string
  lang: 'en' | 'ka'
  bucket: string
  region: string
  counter: number
}) {
  return {
    basePath: '',
    pagePath: 'pages/' + (page || 'index') + '.js',

    pageS3Path: '/' + lang + (page ? '/' + page : '') + '.html',
    storeName: bucket,
    storeRegion: region,
    request: {
      uri: '/' + lang + (page ? '/' + page : ''),
      origin: {
        s3: {
          region,
          domainName: `${bucket}.s3.${region}.amazonaws.com`,
          path: '/' + getStaticPagesPath(buildId)
        }
      }
    } as AWSLambda.CloudFrontRequest,
    eTag: Date.now() - 1000 + counter,
    lastModified: Date.now() - 1000 + counter,
    queueName
  }
}

export async function regeneratePage(lang: 'en' | 'ka', page: string, counter = 0) {
  const buildId = await getBuildId()
  console.log('queue', siteSQS)

  const options = getRegenerationOptions({
    queueName: siteSQS,
    buildId,
    lang,
    page,
    bucket: websiteBucket,
    region: defaultRegion,
    counter
  })

  console.log(options)

  const x = await triggerStaticRegeneration(options as any)

  console.log('event', x)
}

Thanks a lot @kigorw, it works.

For anyone who implements this, make sure you have at least an ISR page (with revalidate property returned by getStaticProps) not only SSG pages, otherwise the regenerate lambda (with the latest build manifest) won't be deployed, as a result, the regeneration won't work as expected, either no regeneration lambda will be created or if you had deployed previously with bundle contain ISR page and now you deploy it without it, the regeneration lambda will not be updated with the latest manifest thus it will pick the previous buildID and not the latest one, even though you provide correct static path with latest buildID.

https://github.dev/serverless-nextjs/serverless-next.js/blob/e6367b585fb98608cd2e9327e2c8d4058ba73b00/packages/serverless-components/nextjs-component/src/component.ts#L551

https://github.dev/serverless-nextjs/serverless-next.js/blob/e6367b585fb98608cd2e9327e2c8d4058ba73b00/packages/libs/lambda/src/handlers/default-handler.ts#L81

ykurniawan avatar Sep 16 '22 08:09 ykurniawan

Any idea if this is being planned for a future release? According to this blog entry (from April 2022) Serverless Cloud already supports On-Demand ISR. Has it simply not been added to serverless-nextjs yet?

ba221400 avatar Jan 22 '23 15:01 ba221400