diff --git a/README.md b/README.md
index ae63e73..99d3beb 100644
--- a/README.md
+++ b/README.md
@@ -17,18 +17,19 @@ _you can treat this project as simpler and configurable version of mentioned ear
## Installing
-In your [Next.js][next-homepage] project, execute:
+In your [Next.js][next-homepage] project, execute:
```sh
npm i next-api-og-image chrome-aws-lambda
# or
yarn add next-api-og-image chrome-aws-lambda
```
+
### Short note about the peer dependencies
> âšī¸ If your serverless function does not fit in the allowed size frames on [Vercel][vercel] **(50MB)**, you may want to install older versions of `chrome-aws-lambda`
-In order to do so, replace `chrome-aws-lambda` _(while adding the dependencies)_ with `chrome-aws-lambda@6.0.0` **(47.6 MB)**
+In order to do so, replace `chrome-aws-lambda` _(while adding the dependencies)_ with `chrome-aws-lambda@6.0.0` **(47.6 MB)**
Please, refer to https://github.com/neg4n/next-api-og-image/issues/23#issuecomment-1090319079 for more info đ
@@ -144,6 +145,30 @@ When strategy is set to `query` and you're sending POST HTTP request with JSON b
2. Set appropiate response message to the client
You can disable this behaviour by setting `dev: { errorsInResponse: false }` in the configuration
+### Hooking the post-generate process
+
+In some scenarios you may want to do something _(in other words - execute some logic)_ **after generation of the image**.
+This can be easily done by providing function to `hook` configuration property. The only parameter is `NextApiRequest` object with `image` attached to it.
+
+example (JavaScript):
+
+```js
+import { withOGImage } from 'next-api-og-image'
+
+export default withOGImage({
+ template: {
+ react: ({ myQueryParam }) =>
đĨ {myQueryParam}
,
+ },
+ dev: {
+ inspectHtml: false,
+ },
+ hook: (innerRequest) => {
+ console.log(innerRequest.image)
+ // will print the generated image on the server as Buffer
+ },
+})
+```
+
### Splitting files
Keeping all the templates inline within [Next.js API route][next-api-routes] should not be problematic, but if you prefer keeping things in separate files you can follow the common pattern of creating files like `my-template.html.js` or `my-template.js` when you define template as react _(naming convention is fully up to you)_ with code e.g.
@@ -187,10 +212,13 @@ const nextApiOgImageConfig = {
quality: 90,
// Width of the image in pixels
width: 1200,
- // Height of the image in pixels
+ // Height of the image in pixels
height: 630,
// 'Cache-Control' HTTP header
cacheControl: 'max-age 3600, must-revalidate',
+ // Hook function that allows to intercept inner NextApiRequest with `ogImage` prop attached.
+ // useful for e.g. saving image in the database after the generation.
+ hook: null,
// NOTE: Options within 'dev' object works only when process.env.NODE_ENV === 'development'
dev: {
// Whether to replace binary data (image/screenshot) with HTML
diff --git a/src/index.ts b/src/index.ts
index e5816d5..e7d79e5 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,4 +1,4 @@
-import type { NextApiRequest, NextApiResponse } from 'next'
+import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
import type { Except, RequireExactlyOne } from 'type-fest'
import type { Page, Viewport } from 'puppeteer-core'
import type { ReactElement } from 'react'
@@ -19,32 +19,37 @@ type ImageType = 'png' | 'jpeg' | 'webp'
type StrategyAwareParams<
T extends StrategyOption = 'query',
StrategyDetails extends string | object = string,
-> = T extends 'body'
+ > = T extends 'body'
? StrategyDetails
: Record>
+type NextApiRequestWithOgImage = {
+ image: string | Buffer
+}
+
export type NextApiOgImageConfig<
Strategy extends StrategyOption,
StrategyDetails extends string | object = string,
-> = {
- template: RequireExactlyOne<
- Partial<{
- html: (params: StrategyAwareParams) => string | Promise
- react: (params: StrategyAwareParams) => ReactElement | Promise
- }>,
- 'html' | 'react'
- >
- strategy?: StrategyOption
- cacheControl?: string
- width?: number
- height?: number
- type?: ImageType
- quality?: number
- dev?: Partial<{
- inspectHtml: boolean
- errorsInResponse: boolean
- }>
-}
+ > = {
+ template: RequireExactlyOne<
+ Partial<{
+ html: (params: StrategyAwareParams) => string | Promise
+ react: (params: StrategyAwareParams) => ReactElement | Promise
+ }>,
+ 'html' | 'react'
+ >
+ strategy?: StrategyOption
+ cacheControl?: string
+ width?: number
+ height?: number
+ type?: ImageType
+ quality?: number
+ hook?: (request: NextApiRequestWithOgImage) => void | Promise,
+ dev?: Partial<{
+ inspectHtml: boolean
+ errorsInResponse: boolean
+ }>
+ }
type BrowserEnvironment = {
envMode: EnvMode
@@ -56,7 +61,7 @@ type BrowserEnvironment = {
export function withOGImage<
Strategy extends StrategyOption = 'query',
StrategyDetails extends string | object = string,
->(options: NextApiOgImageConfig) {
+ >(options: NextApiOgImageConfig) {
const defaultOptions: Except, 'template'> = {
strategy: 'query',
cacheControl: 'max-age 3600, must-revalidate',
@@ -64,6 +69,7 @@ export function withOGImage<
height: 630,
type: 'png',
quality: 90,
+ hook: null,
dev: {
inspectHtml: true,
errorsInResponse: true,
@@ -78,6 +84,7 @@ export function withOGImage<
strategy,
type,
width,
+ hook,
height,
quality,
dev: { inspectHtml, errorsInResponse },
@@ -99,7 +106,7 @@ export function withOGImage<
createImageFactory({ inspectHtml, type, quality }),
)
- return async function (request: NextApiRequest, response: NextApiResponse) {
+ return async function(request: NextApiRequest, response: NextApiResponse) {
checkStrategy(strategy, !isProductionLikeMode(envMode) ? errorsInResponse : false, request, response)
const params = stringifyObjectProps(strategy === 'query' ? request.query : request.body)
@@ -109,16 +116,26 @@ export function withOGImage<
htmlTemplate && !reactTemplate
? await htmlTemplate({ ...params } as StrategyAwareParams)
: renderToStaticMarkup(
- await reactTemplate({ ...params } as StrategyAwareParams),
- )
+ await reactTemplate({ ...params } as StrategyAwareParams),
+ )
+
+ const image = await browserEnvironment.createImage(emojify(html));
+
+ if (!!hook) {
+ const extendedRequest: NextApiRequestWithOgImage = {
+ ...request,
+ image
+ }
+
+ await hook(extendedRequest)
+ }
response.setHeader(
'Content-Type',
!isProductionLikeMode(envMode) && inspectHtml ? 'text/html' : type ? `image/${type}` : 'image/png',
)
response.setHeader('Cache-Control', cacheControl)
-
- response.write(await browserEnvironment.createImage(emojify(html)))
+ response.write(image);
response.end()
}
}
@@ -195,7 +212,7 @@ function emojify(html: string) {
}
function pipe(...functions: Array): () => Promise {
- return async function () {
+ return async function() {
return await functions.reduce(
async (acc, fn) => await fn(await acc),
Promise.resolve({ envMode: process.env.NODE_ENV as EnvMode } as BrowserEnvironment),
@@ -208,14 +225,14 @@ function getChromiumExecutable(browserEnvironment: BrowserEnvironment) {
process.platform === 'win32'
? 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
: process.platform === 'linux'
- ? '/usr/bin/google-chrome'
- : '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
+ ? '/usr/bin/google-chrome'
+ : '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
return { ...browserEnvironment, executable }
}
function prepareWebPageFactory(viewPort: Viewport) {
- return async function (browserEnvironment: BrowserEnvironment) {
+ return async function(browserEnvironment: BrowserEnvironment) {
const { page, envMode, executable } = browserEnvironment
if (page) {
@@ -225,10 +242,10 @@ function prepareWebPageFactory(viewPort: Viewport) {
const chromiumOptions = !isProductionLikeMode(envMode)
? { args: [], executablePath: executable, headless: true }
: {
- args: chrome.args,
- executablePath: await chrome.executablePath,
- headless: chrome.headless,
- }
+ args: chrome.args,
+ executablePath: await chrome.executablePath,
+ headless: chrome.headless,
+ }
const browser = await core.launch(chromiumOptions)
const newPage = await browser.newPage()
@@ -247,12 +264,12 @@ function createImageFactory({
type: ImageType
quality: number
}) {
- return function (browserEnvironment: BrowserEnvironment) {
+ return function(browserEnvironment: BrowserEnvironment) {
const { page, envMode } = browserEnvironment
return {
...browserEnvironment,
- createImage: async function (html: string) {
+ createImage: async function(html: string) {
await page.setContent(html)
const file =
!isProductionLikeMode(envMode) && inspectHtml