diff --git a/_includes/layouts/base.njk b/_includes/layouts/base.njk index b937f21..28c5ef9 100644 --- a/_includes/layouts/base.njk +++ b/_includes/layouts/base.njk @@ -1,14 +1,10 @@ - {# CRITICAL: Capture page.url IMMEDIATELY — Eleventy 3.x race condition (#3183) - causes page.url to change mid-render during parallel processing. - Nunjucks {% set %} captures the VALUE (not a reference), making it immune - to later mutations of the shared page object. This MUST be the first - statement in the template, before any filter calls that could yield. #} - {% set _pageUrl = page.url %} - {% set _ogSlug = (_pageUrl or "") | ogSlug %} - {% set _hasOg = _ogSlug | hasOgImage %} + {# OG image resolution handled by og-fix transform in eleventy.config.js + to bypass Eleventy 3.x parallel rendering race condition (#3183). + Template outputs __OG_IMAGE_PLACEHOLDER__ and __TWITTER_CARD_PLACEHOLDER__ + which the transform replaces using the correct slug derived from outputPath. #} @@ -25,11 +21,10 @@ {% set ogPhoto = ogPhoto[0] %} {% endif %} {% endif %} - - - + + {% if ogPhoto and ogPhoto != "" and (ogPhoto | length) > 10 %} @@ -38,18 +33,16 @@ {% elif contentImage and contentImage != "" and (contentImage | length) > 10 %} - {% elif _hasOg %} - {% else %} - + {% endif %} {# Twitter Card meta tags #} - {% set hasImage = _hasOg or (ogPhoto and ogPhoto != "" and (ogPhoto | length) > 10) or (image and image != "" and (image | length) > 10) or (contentImage and contentImage != "" and (contentImage | length) > 10) %} - + {% set hasExplicitImage = (ogPhoto and ogPhoto != "" and (ogPhoto | length) > 10) or (image and image != "" and (image | length) > 10) or (contentImage and contentImage != "" and (contentImage | length) > 10) %} + {% if ogPhoto and ogPhoto != "" and (ogPhoto | length) > 10 %} @@ -58,8 +51,8 @@ {% elif contentImage and contentImage != "" and (contentImage | length) > 10 %} - {% elif _hasOg %} - + {% else %} + {% endif %} {# Favicon #} @@ -102,17 +95,17 @@ [x-show*="loading"], button[\\@click*="fetch"], button[\\@click*="loadMore"] { display: none !important; } - + - {% if site.markdownAgents.enabled and _pageUrl and _pageUrl.startsWith('/articles/') %} - + {% if site.markdownAgents.enabled and page.url and page.url.startsWith('/articles/') %} + {% endif %} - + @@ -314,7 +307,7 @@
- {% if withSidebar and _pageUrl == "/" and homepageConfig and homepageConfig.sections %} + {% if withSidebar and page.url == "/" and homepageConfig and homepageConfig.sections %} {# Homepage: builder controls its own layout and sidebar #} {{ content | safe }} {% elif withSidebar %} diff --git a/eleventy.config.js b/eleventy.config.js index ec2f363..68a5ed6 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -240,6 +240,55 @@ export default function (eleventyConfig) { return content; }); + // Fix OG image meta tags post-rendering — bypasses Eleventy 3.x race condition (#3183). + // page.url is unreliable during parallel rendering, but outputPath IS correct + // since files are written to the correct location. Derives the OG slug from + // outputPath and replaces placeholders emitted by base.njk. + eleventyConfig.addTransform("og-fix", function (content, outputPath) { + if (!outputPath || !outputPath.endsWith(".html")) return content; + + // Derive correct page URL and OG slug from outputPath (immune to race condition) + // Content pages match: .../type/yyyy/MM/dd/slug/index.html + const dateMatch = outputPath.match( + /\/([\w-]+)\/(\d{4})\/(\d{2})\/(\d{2})\/([\w-]+)\/index\.html$/ + ); + + if (dateMatch) { + const [, type, year, month, day, slug] = dateMatch; + const pageUrlPath = `/${type}/${year}/${month}/${day}/${slug}/`; + const correctFullUrl = `${siteUrl}${pageUrlPath}`; + const ogSlug = `${year}-${month}-${day}-${slug}`; + const hasOg = existsSync(resolve(__dirname, ".cache", "og", `${ogSlug}.png`)); + const ogImageUrl = hasOg + ? `${siteUrl}/og/${ogSlug}.png` + : `${siteUrl}/images/og-default.png`; + const twitterCard = hasOg ? "summary_large_image" : "summary"; + + // Fix og:url and canonical (also affected by race condition) + content = content.replace( + /(