Hello everyone,
I am currently running a next.js standalone application in a production environment. Thanks to your hard work, we have been able to generate stunning OG images, greatly encouraging users to share more on social media. I am truly grateful for this.
However, serving the standalone application has introduced some constraints related to the edge runtime. As a result, we have been utilizing satori and sharp in the node.js runtime instead of relying on edge runtime and next/og.
The performance issue has been a concern, though. Combining several React components and 2-3 fonts has resulted in more delay than anticipated. Executing the following wrk command on a MacBook M1 Pro showed a performance of 10-15 RPS. The actual production environment, with less CPU resources, showed about 5 RPS:
$ wrk -t10 -c 30 -d30s http://localhost:3000/og/review\?updatedAt\=2024-01-29T14:30:22 Running 30s test @ http://localhost:3000/og/review\?updatedAt=2024-01-29T14:30:22 10 threads and 30 connections Thread Stats Avg Stdev Max +/- Stdev Latency 1.53s 339.74ms 2.00s 66.77% Req/Sec 2.18 2.99 20.00 91.64% 480 requests in 30.10s, 171.63MB read Socket errors: connect 0, read 0, write 0, timeout 149 Requests/sec: 15.95 Transfer/sec: 5.70MB
Adding the NODE_OPTIONS=--inspect
option for profiling revealed that the addFonts()
function, called during the satori constructor execution, was being repeatedly executed. (We have conducted profiling using Chrome DevTools as shown below.)
[1] Bottom-Up without grouping
[2] Chrome DevTools - Performance tap
Tracing results suggest that the bottleneck mostly occurs when calling the addFonts()
function from the satori()
constructor. While the actual image generation takes only about 20-50ms, 25ms is spent on addFonts()
. Despite having the font buffer declared as a global variable, unnecessary operations are executed during each satori constructor call due to the processing of opentype fonts. It appears there's no interface for injecting the results of addFonts()
, thus leading to the repeated execution of this function.
addFonts()
leads to event loop delaysReferencing src/fonts.ts #L135
, as shown below:
public addFonts(fontOptions: FontOptions[]) { // ... const font = opentype.parse( // Buffer to ArrayBuffer. 'buffer' in data ? data.buffer.slice( data.byteOffset, data.byteOffset + data.byteLength ) : data, // @ts-ignore { lowMemory: true } ) // ... this.fonts.get(_name).push([font, fontOption.weight, fontOption.style]) // ... }
It seems each execution of satori() assigns the result of opentype.parse() to an internal variable. I'm curious if there's a way to declare the this.fonts variable globally and either inject it from outside or reuse it in some manner.
Since we consistently use 2-3 specific fonts, minimizing the number of addFonts()
calls is expected to improve throughput.
This is our /pages/api/og/[id].tsx
.
const [boldFontFile, semiBoldFontFile, ratingStar, inflearnLogo] = await Promise.all([ getFontData("bold"), getFontData("semiBold"), readFile(path.resolve("public/images/rating_star.png")), readFile(path.resolve("public/images/logo.png")), ]); export default async function handler( request: NextApiRequest, response: NextApiResponse ) { try { const svg = await satori(JSX, { width: 1200, height: 630, embedFont: true, fonts: [ { name: "Pretendard", data: boldFontFile, weight: 700, style: "normal", }, { name: "Pretendard", data: semiBoldFontFile, weight: 600, style: "normal", }, ], } );
I apologize for my bad English and hope my message is clear. Please feel free to point out any imperfections in my ideas or offer any assistance you can. Thank you for taking the time to read this.
Additional ContextMaybe profiles image can be additional context.
jojoldu2inflab, LeeMoonki, shinyuna, rokinflearn, rlaisqlsinflab and 21 more
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4