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

Nextjs 12 plugin version for manual regerneration of page ISR #2494

Open
AlessandroMaro80 opened this issue Oct 7, 2022 · 10 comments
Open

Comments

@AlessandroMaro80
Copy link

Hello,
when will the plugin version for nextjs 12 come out?
We would need it for the manual page regeneration feature
Thank you very much :)

@apollisa
Copy link

I will be attempting to work on that, with the help of @Misfits09 and @alexandrepernin! Stay tuned. 🤗

@skymarocchinia
Copy link

Thank you very much guys! :)

@skymarocchinia
Copy link

Any news guys? :)
@apollisa

@GlennStreetman
Copy link

I was able to get manual regeneration running after following issue #2417. The regeneratePage function shown below is run for each page in my /api/revalidate endpoint.

You will need to npm/yarn add/install @sls-next/lambda-at-edge
Check your cloudwatch logs and s3 bucket if you run into any trouble. You might need to the getRegenerationOptions filepaths depending on how your project laid out.

import { triggerStaticRegeneration } from '@sls-next/lambda-at-edge/dist/lib/triggerStaticRegeneration'
import S3 from "aws-sdk/clients/s3";

const region = process.env.defaultRegion
const siteSQS = process.env.siteSQS
const websiteBucket = process.env.bucketname
const accessKeyId = process.env.AWS_ACCESS_KEY;
const secretAccessKey = process.env.AWS_SECRET_KEY;

export async function getBuildId(): Promise<string> {

    const s3 = new S3({
        region,
        accessKeyId,
        secretAccessKey,
    });

    const data = function() {

        const downloadParams = {
            Key: 'BUILD_ID',
            Bucket: websiteBucket,
        };
        
        return new Promise((res)=>{

        s3.getObject(downloadParams, (err,data)=>{
            if(err) {
                console.log("get BUILD_ID Error: ", err);
            } else {
                res(data.Body.toString())
            }
        })

    })
    }
    const file = await data()
    // @ts-ignore
    return (file || '')
}

export function getStaticPagesPath(buildId: string) {
  return `static-pages/${buildId}`
}

export function getRegenerationOptions({
  queueName,
  buildId,
  page,
  bucket,
  region,
  counter
}: {
  queueName: string
  buildId: string
  page: string
  bucket: string
  region: string
  counter: number
}) {
  return {
    basePath: '',
    pagePath: 'pages/' + (page || 'index') + '.js',
    pageS3Path: (page ?  page : 'index') + '.html',
    storeName: bucket,
    storeRegion: region,
    request: {
      uri:  (page ? "/" + page : '/'),
      origin: {
        s3: {
          region,
          domainName: `${bucket}.s3.${region}.amazonaws.com`,
          path: '/' + getStaticPagesPath(buildId)
        }
      }
    },
    eTag: Date.now() - 1000 + counter,
    lastModified: Date.now() - 1000 + counter,
    queueName
  }
}
export async function regeneratePage( rawpage: string, counter = 0) { 
    const buildId = await getBuildId()
    const page:string = rawpage.substring(1)
    const options = getRegenerationOptions({
    queueName: siteSQS,
    buildId,
    page,
    bucket: websiteBucket,
    region: region,
    counter
    })

    console.log(
        "params:", 
        {
            queueName: siteSQS,
            buildId,
            page,
            bucket: websiteBucket,
            region: region,
            counter
        },
        "returnd options", 
        options)

    await triggerStaticRegeneration(options as any)

    return true
}

@AntonShanyuk
Copy link

I was able to get manual regeneration running after following issue #2417. The regeneratePage function shown below is run for each page in my /api/revalidate endpoint.

You will need to npm/yarn add/install @sls-next/lambda-at-edge Check your cloudwatch logs and s3 bucket if you run into any trouble. You might need to the getRegenerationOptions filepaths depending on how your project laid out.

import { triggerStaticRegeneration } from '@sls-next/lambda-at-edge/dist/lib/triggerStaticRegeneration'
import S3 from "aws-sdk/clients/s3";

const region = process.env.defaultRegion
const siteSQS = process.env.siteSQS
const websiteBucket = process.env.bucketname
const accessKeyId = process.env.AWS_ACCESS_KEY;
const secretAccessKey = process.env.AWS_SECRET_KEY;

export async function getBuildId(): Promise<string> {

    const s3 = new S3({
        region,
        accessKeyId,
        secretAccessKey,
    });

    const data = function() {

        const downloadParams = {
            Key: 'BUILD_ID',
            Bucket: websiteBucket,
        };
        
        return new Promise((res)=>{

        s3.getObject(downloadParams, (err,data)=>{
            if(err) {
                console.log("get BUILD_ID Error: ", err);
            } else {
                res(data.Body.toString())
            }
        })

    })
    }
    const file = await data()
    // @ts-ignore
    return (file || '')
}

export function getStaticPagesPath(buildId: string) {
  return `static-pages/${buildId}`
}

export function getRegenerationOptions({
  queueName,
  buildId,
  page,
  bucket,
  region,
  counter
}: {
  queueName: string
  buildId: string
  page: string
  bucket: string
  region: string
  counter: number
}) {
  return {
    basePath: '',
    pagePath: 'pages/' + (page || 'index') + '.js',
    pageS3Path: (page ?  page : 'index') + '.html',
    storeName: bucket,
    storeRegion: region,
    request: {
      uri:  (page ? "/" + page : '/'),
      origin: {
        s3: {
          region,
          domainName: `${bucket}.s3.${region}.amazonaws.com`,
          path: '/' + getStaticPagesPath(buildId)
        }
      }
    },
    eTag: Date.now() - 1000 + counter,
    lastModified: Date.now() - 1000 + counter,
    queueName
  }
}
export async function regeneratePage( rawpage: string, counter = 0) { 
    const buildId = await getBuildId()
    const page:string = rawpage.substring(1)
    const options = getRegenerationOptions({
    queueName: siteSQS,
    buildId,
    page,
    bucket: websiteBucket,
    region: region,
    counter
    })

    console.log(
        "params:", 
        {
            queueName: siteSQS,
            buildId,
            page,
            bucket: websiteBucket,
            region: region,
            counter
        },
        "returnd options", 
        options)

    await triggerStaticRegeneration(options as any)

    return true
}

Hey @GlennStreetman thanks for sharing the example. Where do I get the params bucketname, siteSQS and defaultRegion? It seems like they are not present in env variables

@GlennStreetman
Copy link

I was able to get manual regeneration running after following issue #2417. The regeneratePage function shown below is run for each page in my /api/revalidate endpoint.
You will need to npm/yarn add/install @sls-next/lambda-at-edge Check your cloudwatch logs and s3 bucket if you run into any trouble. You might need to the getRegenerationOptions filepaths depending on how your project laid out.

import { triggerStaticRegeneration } from '@sls-next/lambda-at-edge/dist/lib/triggerStaticRegeneration'
import S3 from "aws-sdk/clients/s3";

const region = process.env.defaultRegion
const siteSQS = process.env.siteSQS
const websiteBucket = process.env.bucketname
const accessKeyId = process.env.AWS_ACCESS_KEY;
const secretAccessKey = process.env.AWS_SECRET_KEY;

export async function getBuildId(): Promise<string> {

    const s3 = new S3({
        region,
        accessKeyId,
        secretAccessKey,
    });

    const data = function() {

        const downloadParams = {
            Key: 'BUILD_ID',
            Bucket: websiteBucket,
        };
        
        return new Promise((res)=>{

        s3.getObject(downloadParams, (err,data)=>{
            if(err) {
                console.log("get BUILD_ID Error: ", err);
            } else {
                res(data.Body.toString())
            }
        })

    })
    }
    const file = await data()
    // @ts-ignore
    return (file || '')
}

export function getStaticPagesPath(buildId: string) {
  return `static-pages/${buildId}`
}

export function getRegenerationOptions({
  queueName,
  buildId,
  page,
  bucket,
  region,
  counter
}: {
  queueName: string
  buildId: string
  page: string
  bucket: string
  region: string
  counter: number
}) {
  return {
    basePath: '',
    pagePath: 'pages/' + (page || 'index') + '.js',
    pageS3Path: (page ?  page : 'index') + '.html',
    storeName: bucket,
    storeRegion: region,
    request: {
      uri:  (page ? "/" + page : '/'),
      origin: {
        s3: {
          region,
          domainName: `${bucket}.s3.${region}.amazonaws.com`,
          path: '/' + getStaticPagesPath(buildId)
        }
      }
    },
    eTag: Date.now() - 1000 + counter,
    lastModified: Date.now() - 1000 + counter,
    queueName
  }
}
export async function regeneratePage( rawpage: string, counter = 0) { 
    const buildId = await getBuildId()
    const page:string = rawpage.substring(1)
    const options = getRegenerationOptions({
    queueName: siteSQS,
    buildId,
    page,
    bucket: websiteBucket,
    region: region,
    counter
    })

    console.log(
        "params:", 
        {
            queueName: siteSQS,
            buildId,
            page,
            bucket: websiteBucket,
            region: region,
            counter
        },
        "returnd options", 
        options)

    await triggerStaticRegeneration(options as any)

    return true
}

Hey @GlennStreetman thanks for sharing the example. Where do I get the params bucketname, siteSQS and defaultRegion? It seems like they are not present in env variables

  • Bucketname: The s3 bucket for your serverless deployment.
  • defaultRegion: The AWS regions, us-east-1 is an example. This should be the region your lambda is deployed to.
  • siteSQS: The simple query service queue related to your deployment.

I added each of the above to my .env file. These names didn't exist before my first deploy so i had to deploy serverless, go into the AWS console and update my variables, then update my .env file and redeploy.

Remember that for this to all work you will need to make sure permissions are all setup correctly in IAM for Lambda, S3, and SMS.

@UltimateForm
Copy link

UltimateForm commented Jan 19, 2023

@GlennStreetman i think i understand the code, but im confused, how does this work
pagePath: 'pages/' + (page || 'index') + '.js'
On my build the pages are appended unique ids (them seem unique) on the chunks folder (which is the only place with these javascript files)

my homepage js file for example is located at \static\chunks\pages\index-a29c946d7927b337.js

i can deploy this function as a lambda and call it successfully and send the SQS message but the regeneration lambda can't process the messaage because it cannot find the js file

Is it meant to find the js file inside the chunks folder? if so, how does it know which one it is with just the page name?

@UltimateForm
Copy link

UltimateForm commented Jan 19, 2023

update: disregard my last comment, sls-next is doing something to find the right js file, my issue was with having dynamically generated pages

but @GlennStreetman what is the usage of the counter? i see it being passed around

@ba221400
Copy link

ba221400 commented Jan 22, 2023

@GlennStreetman thanks for your insight here. Would you be able to share a code snippet of how you're calling this within the /api/revalidate endpoint?

@apollisa any update on an official update to the serverless-nextjs project?

@GlennStreetman
Copy link

@ba221400 The API endpoint shown below is called by Strapi CMS. staticRoutes & revalidateLookup is specific to my small hobby app. Your mapping will definitely be different. The "const update = " is cleanup needed for my particular use case. Hope this helps.

import {regeneratePage} from '../../lib/revalidateLambda'

interface req {
    body: reqPayload
    headers: any
}

interface reqPayload { 
    event: string, //entry.update
    createdAt: string,
    model: string, //faq, holiday, service, team, banner-text, contact, google-map, homepage-intro, our-story, page-title, service-home, banner-image, service-home, site-link, site-text
    entry: {[key:string]: any}, //based upon model
    headers: any
}

const staticRoutes = ["/", "/about",  "/calendar", "/careers",  "/contact", "/resumeSubmitted", "/service", "/thankyou" ];

const exampleAPIEndPoint = async (req:req, res) => {
        try {

            if (req.headers.secret === process.env.NEXT_REVALIDATE) {
                
                const update = req?.body?.model ? req.body.model.replaceAll('-', '') : ''
                const serviceName = update === 'service' ? req.body.entry.name : 'false'
                console.log('revalidate request received: ', update)

                const revalidateLookup = {
                    faq: staticRoutes, 
                    holiday: ["/calendar"], 
                    holliday: ["/calendar"], 
                    service: function(){
                        // console.log('revalidating Now', serviceName)
                        return [`/service/${serviceName.replaceAll(' ', '_')}`, '/service']
                    }(), 
                    team: ['/about'], 
                    bannertext: staticRoutes, 
                    contact: staticRoutes, 
                    googleMap: [ "/about", "/contact"], 
                    homepageintro: ['/'], 
                    ourstory: ['/', '/about'], 
                    pagetitle: ['/about'], 
                    servicehome: ["/service"], 
                    bannerimage: staticRoutes, 
                    sitelink: staticRoutes, 
                    sitetext: staticRoutes,
                }

                const revalidateList = revalidateLookup?.[update] || []
                for(const el of revalidateList){
                    console.log('revaliding:', el)
                    await regeneratePage(`${el}`)
                    // await res.revalidate(`${el}`);
                    console.log( el, 'revalid complete')
                }
                //here
                res.status(200).json({update: update})
            } else {
                console.log('revalidateHook: Missing Secret')
                res.status(400).json({ msg: "Error" });
            }
        } catch (err) {
            console.log("/api/revalidateHook Error:", req.body ,err);
            res.status(400).json({ msg: "Error" });
        }

};

export default exampleAPIEndPoint;

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

7 participants