Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support on-demand purge nextjs cache released in 12.1 #2417

Open
davidh99720 opened this issue Mar 30, 2022 · 17 comments
Open

Support on-demand purge nextjs cache released in 12.1 #2417

davidh99720 opened this issue Mar 30, 2022 · 17 comments

Comments

@davidh99720
Copy link

davidh99720 commented Mar 30, 2022

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

@m0rph3u5-zip
Copy link

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.

@jlaramie
Copy link
Contributor

jlaramie commented Apr 5, 2022

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

@m0rph3u5-zip
Copy link

same response here!

@kigorw
Copy link

kigorw commented Apr 23, 2022

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

@mtwalsh
Copy link

mtwalsh commented May 4, 2022

Very new to Serverless Next.js but it seems like a lot of the groundwork for this has been completed in #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!

@kigorw
Copy link

kigorw commented May 4, 2022

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

@mtwalsh
Copy link

mtwalsh commented May 4, 2022

Wow, thanks for the quick response @kigorw.

@m0rph3u5-zip
Copy link

m0rph3u5-zip commented May 11, 2022

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!

@RodrigoTomeES
Copy link

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

@kigorw
Copy link

kigorw commented Jun 1, 2022

@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.

@RodrigoTomeES
Copy link

@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?

@rafaelone
Copy link

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

@sitezen
Copy link

sitezen commented Sep 12, 2022

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

@AlejandroJAguilarP
Copy link

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

@sitezen
Copy link

sitezen commented Sep 14, 2022

@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

@ykurniawan
Copy link

ykurniawan commented Sep 16, 2022

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

@ba221400
Copy link

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests