-
-
Notifications
You must be signed in to change notification settings - Fork 33
Optimize Image Generation Performance - Reduce Load Time by 80%+ #142
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,109 +1,165 @@ | ||||||||||||||
| import satori from 'satori'; | ||||||||||||||
| import { NextResponse } from 'next/server'; | ||||||||||||||
| import RenderSVG from '@/components/RenderSVG'; // Ensure this path is correct | ||||||||||||||
| import RenderSVG from '@/components/RenderSVG'; | ||||||||||||||
|
|
||||||||||||||
| export const runtime = 'edge'; | ||||||||||||||
|
|
||||||||||||||
| // Font cache to avoid repeated fetches | ||||||||||||||
| const fontCache = new Map(); | ||||||||||||||
|
|
||||||||||||||
| // Font mapping for lazy loading | ||||||||||||||
| const FONT_FILES = { | ||||||||||||||
| 'Helvetica': '/Helvetica.otf', | ||||||||||||||
| 'Arial': '/Arial.ttf', | ||||||||||||||
| 'TimesNewRoman': '/TimesNewRoman.ttf', | ||||||||||||||
| 'Calibri': '/Calibri.ttf', | ||||||||||||||
| 'Verdana': '/Verdana.ttf', | ||||||||||||||
| 'Cascadia': '/CascadiaCode-Bold.otf', | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| // Default font if none specified | ||||||||||||||
| const DEFAULT_FONT = 'Arial'; | ||||||||||||||
|
|
||||||||||||||
| /** | ||||||||||||||
| * Lazy load only the required font instead of all fonts | ||||||||||||||
| * This significantly reduces image generation time | ||||||||||||||
| */ | ||||||||||||||
| async function loadFont(fontName, baseUrl) { | ||||||||||||||
| const cacheKey = `${fontName}-${baseUrl}`; | ||||||||||||||
|
||||||||||||||
| const cacheKey = `${fontName}-${baseUrl}`; | |
| const cacheKey = fontName; |
Copilot
AI
Nov 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing response validation: The fetch on line 39 doesn't check if the response is successful (e.g., res.ok). If the font file doesn't exist (404) or there's a server error (500), the response will still be converted to arrayBuffer, potentially creating a corrupted font. Add response validation: if (!res.ok) throw new Error(\Font fetch failed: ${res.status}`);before callingres.arrayBuffer()`.
| const fontData = await fetch(fontUrl).then((res) => res.arrayBuffer()); | |
| const res = await fetch(fontUrl); | |
| if (!res.ok) { | |
| throw new Error(`Font fetch failed: ${res.status}`); | |
| } | |
| const fontData = await res.arrayBuffer(); |
Copilot
AI
Nov 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Font name mismatch: When fontName is not found in FONT_FILES, the code uses the default font file but keeps the original fontName in the config (line 42). This creates a mismatch where the font data is for 'Arial' but the name is something else. This should use the DEFAULT_FONT name when falling back. Change line 42 to: name: FONT_FILES[fontName] ? fontName : DEFAULT_FONT,
| name: fontName, | |
| name: FONT_FILES[fontName] ? fontName : DEFAULT_FONT, |
Copilot
AI
Nov 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The style property should be set to 'normal' instead of 'bold'. Font style typically refers to 'normal', 'italic', or 'oblique', not 'bold'. The weight: 800 property already handles the bold styling. Setting style: 'bold' may cause issues with font rendering in satori.
| style: fontName === 'Cascadia' ? 'bold' : 'normal', | |
| style: 'normal', |
Copilot
AI
Nov 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential infinite recursion risk: If the DEFAULT_FONT fails to load and throws an error, there's no additional safeguard. Consider adding a flag or counter to prevent infinite recursion, or handle the default font failure case explicitly without recursion.
Copilot
AI
Nov 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation states "Parallel config and font fetching" but the implementation is sequential: config is fetched on line 99 and awaited on line 109, then font is loaded on line 133. To achieve parallel fetching, you could start the font loading immediately after determining which font to use (which requires knowing the default), or restructure the code to fetch both concurrently using Promise.all().
Copilot
AI
Nov 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fallback to DEFAULT_FONT is redundant here since config.font already has a fallback to DEFAULT_FONT on line 114. This line can be simplified to const fontToLoad = config.font;
| const fontToLoad = config.font || DEFAULT_FONT; | |
| const fontToLoad = config.font; |
Copilot
AI
Nov 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Caching issue: Setting max-age=31536000 (1 year) for dynamic content is problematic. The image is generated based on user data (star_count, fork_count, repo_count, UserName, Tagline, etc.) that can change over time. With a 1-year cache, users won't see updates to their profile data until the cache expires. Consider using a shorter cache duration (e.g., hours or days) or implementing cache invalidation based on the update field from the config data.
| 'cache-control': `public, immutable, no-transform, max-age=31536000`, // Cache for 1 year | |
| 'cache-control': `public, immutable, no-transform, max-age=21600`, // Cache for 6 hours |
Copilot
AI
Nov 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security consideration: Exposing error.message in the API response could leak sensitive information about the server's internal workings or file system paths. Consider logging the full error details server-side but returning a generic error message to the client, or sanitizing the error message before including it in the response.
| error: "Failed to generate image", | |
| details: error.message | |
| error: "Failed to generate image" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Module-level state in Edge Runtime: Using a module-level
Map()for caching in Edge runtime can lead to unpredictable behavior. Edge runtime instances may not persist state consistently across requests. Consider using a platform-specific cache (like Vercel's Edge Cache API) or documenting this limitation. The cache might not work as expected in production Edge deployments.