From 86cbc1ee5d55aa1b47400dcbfa3dd67f18c0be5a Mon Sep 17 00:00:00 2001 From: Ricardo Date: Wed, 18 Feb 2026 08:49:11 +0100 Subject: [PATCH] fix: run OG image generation in subprocess to prevent OOM kill The OG generation in the eleventy.before hook consumed too much memory alongside Eleventy's data cascade, causing the Eleventy process to be OOM-killed on Cloudron. Fix by running OG generation in a separate child process with its own 768MB heap limit. Also write the manifest incrementally (every 10 images) to preserve progress if interrupted. --- eleventy.config.js | 13 ++++++++++--- lib/og-cli.js | 19 +++++++++++++++++++ lib/og.js | 6 ++++++ 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 lib/og-cli.js diff --git a/eleventy.config.js b/eleventy.config.js index 36bb6af..55335a9 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -10,7 +10,6 @@ import { minify } from "html-minifier-terser"; import { createHash } from "crypto"; import { execFileSync } from "child_process"; import { readFileSync, existsSync } from "fs"; -import { generateOgImages } from "./lib/og.js"; import { resolve, dirname } from "path"; import { fileURLToPath } from "url"; @@ -526,12 +525,20 @@ export default function (eleventyConfig) { }); // Generate OpenGraph images for posts without photos - eleventyConfig.on("eleventy.before", async () => { + eleventyConfig.on("eleventy.before", () => { const contentDir = resolve(__dirname, "content"); const cacheDir = resolve(__dirname, ".cache"); const siteName = process.env.SITE_NAME || "My IndieWeb Blog"; try { - await generateOgImages(contentDir, cacheDir, siteName); + execFileSync(process.execPath, [ + resolve(__dirname, "lib", "og-cli.js"), + contentDir, + cacheDir, + siteName, + ], { + stdio: "inherit", + env: { ...process.env, NODE_OPTIONS: "--max-old-space-size=768" }, + }); } catch (err) { console.error("[og] Image generation failed:", err.message); } diff --git a/lib/og-cli.js b/lib/og-cli.js new file mode 100644 index 0000000..421b5b0 --- /dev/null +++ b/lib/og-cli.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node + +/** + * CLI entry point for OG image generation. + * Runs as a separate process to isolate memory from Eleventy. + * + * Usage: node lib/og-cli.js + */ + +import { generateOgImages } from "./og.js"; + +const [contentDir, cacheDir, siteName] = process.argv.slice(2); + +if (!contentDir || !cacheDir || !siteName) { + console.error("[og] Usage: node og-cli.js "); + process.exit(1); +} + +await generateOgImages(contentDir, cacheDir, siteName); diff --git a/lib/og.js b/lib/og.js index fdffe94..59fefc1 100644 --- a/lib/og.js +++ b/lib/og.js @@ -288,6 +288,7 @@ export async function generateOgImages(contentDir, cacheDir, siteName) { let generated = 0; let skipped = 0; const newManifest = {}; + const SAVE_INTERVAL = 10; for (const filePath of mdFiles) { const raw = readFileSync(filePath, "utf8"); @@ -320,6 +321,11 @@ export async function generateOgImages(contentDir, cacheDir, siteName) { writeFileSync(join(ogDir, `${slug}.png`), pngBuffer); newManifest[slug] = { title: slug, hash }; generated++; + + // Save manifest periodically to preserve progress + if (generated % SAVE_INTERVAL === 0) { + writeFileSync(manifestPath, JSON.stringify(newManifest, null, 2)); + } } writeFileSync(manifestPath, JSON.stringify(newManifest, null, 2));