mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2026-04-17 10:28:46 -04:00
fix: load OG image fonts from @fontsource/inter instead of Google Fonts
Vercel's build infra was intermittently hitting ETIMEDOUT against fonts.googleapis.com, causing OG image generation errors during prerender. Ship the Inter .woff files with the function via outputFileTracingIncludes so the build is network-free. Also add turbopackIgnore hints on process.cwd() calls to silence an NFT warning that was over-tracing next.config.ts into the route bundle. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,9 +5,24 @@ import { notFound } from "next/navigation";
|
||||
import { ImageResponse } from "next/og";
|
||||
|
||||
import siteConfig from "@/lib/config/site";
|
||||
import { loadGoogleFont } from "@/lib/og-utils";
|
||||
import { getFrontMatter, getSlugs, POSTS_DIR } from "@/lib/posts";
|
||||
|
||||
// Reading Inter fonts from the local @fontsource/inter package (instead of
|
||||
// fetching Google Fonts at build time) avoids flaky network timeouts on
|
||||
// Vercel's build infra that caused OG image generation to fail intermittently.
|
||||
// Satori supports .woff but not .woff2. The two file paths are listed explicitly
|
||||
// in next.config.ts under `outputFileTracingIncludes` so NFT ships them with
|
||||
// the function output.
|
||||
const loadInterFont = async (weight: 400 | 600): Promise<ArrayBuffer> => {
|
||||
const fontPath = path.join(
|
||||
/* turbopackIgnore: true */ process.cwd(),
|
||||
"node_modules/@fontsource/inter/files",
|
||||
`inter-latin-${weight}-normal.woff`,
|
||||
);
|
||||
const buffer = await fs.promises.readFile(fontPath);
|
||||
return Uint8Array.from(buffer).buffer;
|
||||
};
|
||||
|
||||
export const contentType = "image/png";
|
||||
export const size = {
|
||||
// https://developers.facebook.com/docs/sharing/webmasters/images/
|
||||
@@ -28,7 +43,7 @@ const getLocalImage = async (src: string): Promise<ArrayBuffer | string> => {
|
||||
// https://stackoverflow.com/questions/5775469/whats-the-valid-way-to-include-an-image-with-no-src/14115340#14115340
|
||||
const NO_IMAGE = "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";
|
||||
|
||||
const imagePath = path.join(process.cwd(), src);
|
||||
const imagePath = path.join(/* turbopackIgnore: true */ process.cwd(), src);
|
||||
|
||||
try {
|
||||
if (!fs.existsSync(imagePath)) {
|
||||
@@ -67,10 +82,7 @@ const OpenGraphImage = async ({ params }: { params: Promise<{ slug: string }> })
|
||||
getLocalImage("app/avatar.jpg"),
|
||||
]);
|
||||
|
||||
const [fontRegular, fontSemibold] = await Promise.all([
|
||||
loadGoogleFont("Inter", 400),
|
||||
loadGoogleFont("Inter", 600),
|
||||
]);
|
||||
const [fontRegular, fontSemibold] = await Promise.all([loadInterFont(400), loadInterFont(600)]);
|
||||
|
||||
// template is HEAVILY inspired by https://og-new.clerkstage.dev/
|
||||
return new ImageResponse(
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { cacheLife } from "next/cache";
|
||||
|
||||
// Load a Google Font from the Google Fonts API
|
||||
// Adapted from https://github.com/brianlovin/briOS/blob/f72dc33a11194de45c80337b22be4560da62ad7e/src/lib/og-utils.tsx#L32
|
||||
export async function loadGoogleFont(font: string, weight: number): Promise<ArrayBuffer> {
|
||||
"use cache";
|
||||
|
||||
const url = `https://fonts.googleapis.com/css2?family=${font}:wght@${weight}`;
|
||||
|
||||
const cssResponse = await fetch(url, {
|
||||
next: {
|
||||
revalidate: 31_536_000, // 1 year
|
||||
},
|
||||
});
|
||||
const css = await cssResponse.text();
|
||||
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/);
|
||||
|
||||
if (resource) {
|
||||
const fontResponse = await fetch(resource[1], {
|
||||
next: {
|
||||
revalidate: 31_536_000, // 1 year
|
||||
},
|
||||
});
|
||||
if (fontResponse.status === 200) {
|
||||
cacheLife("max"); // cache indefinitely if successful
|
||||
return fontResponse.arrayBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Failed to load font: ${font} ${weight}`);
|
||||
}
|
||||
@@ -18,7 +18,12 @@ const nextConfig = {
|
||||
],
|
||||
},
|
||||
outputFileTracingIncludes: {
|
||||
"/notes/[slug]/opengraph-image": ["./notes/**/*", "./app/opengraph-image.jpg"],
|
||||
"/notes/[slug]/opengraph-image": [
|
||||
"./notes/**/*",
|
||||
"./app/opengraph-image.jpg",
|
||||
"./node_modules/**/@fontsource/inter/files/inter-latin-400-normal.woff",
|
||||
"./node_modules/**/@fontsource/inter/files/inter-latin-600-normal.woff",
|
||||
],
|
||||
},
|
||||
productionBrowserSourceMaps: true,
|
||||
experimental: {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.3.0",
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@mdx-js/loader": "^3.1.1",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@next/mdx": "16.2.3",
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -11,6 +11,9 @@ importers:
|
||||
'@base-ui/react':
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.0(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
'@fontsource/inter':
|
||||
specifier: ^5.2.8
|
||||
version: 5.2.8
|
||||
'@mdx-js/loader':
|
||||
specifier: ^3.1.1
|
||||
version: 3.1.1
|
||||
@@ -949,6 +952,9 @@ packages:
|
||||
'@floating-ui/utils@0.2.11':
|
||||
resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
|
||||
|
||||
'@fontsource/inter@5.2.8':
|
||||
resolution: {integrity: sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==}
|
||||
|
||||
'@hono/node-server@1.19.13':
|
||||
resolution: {integrity: sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==}
|
||||
engines: {node: '>=18.14.1'}
|
||||
@@ -4702,6 +4708,8 @@ snapshots:
|
||||
|
||||
'@floating-ui/utils@0.2.11': {}
|
||||
|
||||
'@fontsource/inter@5.2.8': {}
|
||||
|
||||
'@hono/node-server@1.19.13(hono@4.12.12)':
|
||||
dependencies:
|
||||
hono: 4.12.12
|
||||
|
||||
Reference in New Issue
Block a user