From a7e68ee9411a443052a23d4a03d1d8ead6112758 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Tue, 24 Feb 2026 17:55:31 +0100 Subject: [PATCH] fix: derive ogSlug from inputPath to fix Eleventy 3.x race condition page.url in eleventyComputed returns URLs from other pages being processed concurrently in Eleventy 3.x parallel rendering. This caused OG images to show wrong post types and titles (e.g., a note showing "Reply" badge from a completely different post). Fix: use page.inputPath (physical file path) which is always correct, matching the approach already used by the permalink computation. --- _data/eleventyComputed.js | 50 ++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/_data/eleventyComputed.js b/_data/eleventyComputed.js index d428e56..8eed4ea 100644 --- a/_data/eleventyComputed.js +++ b/_data/eleventyComputed.js @@ -1,10 +1,13 @@ /** - * Computed data resolved during the data cascade (sequential, per-page). + * Computed data resolved during the data cascade. * - * Eleventy 3.x renders Nunjucks templates in parallel, which causes - * `page.url` and `page.fileSlug` to return wrong values when read - * via {% set %} in templates. By computing OG-related values here, - * they are resolved before parallel rendering begins. + * Eleventy 3.x parallel rendering causes `page.url` and `page.fileSlug` + * to return values from OTHER pages being processed concurrently. + * This affects both templates and eleventyComputed functions. + * + * Fix: ALL computed values derive from `page.inputPath` (the physical file + * path on disk), which is always correct regardless of parallel rendering. + * NEVER use `page.url` or `page.fileSlug` here. * * See: https://github.com/11ty/eleventy/issues/3183 */ @@ -47,30 +50,39 @@ export default { return data.permalink; }, - // OG image slug — must reconstruct date-prefixed filename from URL segments. + // OG image slug — derive from inputPath (physical file), NOT page.url. + // page.url suffers from Eleventy 3.x parallel rendering race conditions + // where it can return the URL of a DIFFERENT page being processed concurrently. + // inputPath is the physical file path, which is always correct. // OG images are generated as {yyyy}-{MM}-{dd}-{slug}.png by lib/og.js. - // With new URL structure /type/yyyy/MM/dd/slug/, we reconstruct the filename. ogSlug: (data) => { - const url = data.page?.url || ""; - const segments = url.split("/").filter(Boolean); - // Date-based URL: /type/yyyy/MM/dd/slug/ → 5 segments - if (segments.length === 5) { - const [, year, month, day, slug] = segments; + const inputPath = data.page?.inputPath || ""; + const match = inputPath.match( + /content\/([^/]+)\/(\d{4})-(\d{2})-(\d{2})-(.+)\.md$/, + ); + if (match) { + const [, , year, month, day, slug] = match; return `${year}-${month}-${day}-${slug}`; } - // Fallback: last segment (for pages, legacy URLs) - return segments[segments.length - 1] || ""; + // Fallback for pages/root files: use last path segment + const segments = inputPath.split("/").filter(Boolean); + const last = segments[segments.length - 1] || ""; + return last.replace(/\.md$/, ""); }, hasOgImage: (data) => { - const url = data.page?.url || ""; - const segments = url.split("/").filter(Boolean); + const inputPath = data.page?.inputPath || ""; + const match = inputPath.match( + /content\/([^/]+)\/(\d{4})-(\d{2})-(\d{2})-(.+)\.md$/, + ); let slug; - if (segments.length === 5) { - const [, year, month, day, s] = segments; + if (match) { + const [, , year, month, day, s] = match; slug = `${year}-${month}-${day}-${s}`; } else { - slug = segments[segments.length - 1] || ""; + const segments = inputPath.split("/").filter(Boolean); + const last = segments[segments.length - 1] || ""; + slug = last.replace(/\.md$/, ""); } if (!slug) return false; const ogPath = resolve(__dirname, "..", ".cache", "og", `${slug}.png`);