fix: use Eleventy transform to resolve OG images from outputPath

The Eleventy 3.x parallel rendering race condition (#3183) makes
page.url unreliable in templates — it changes between lines during
concurrent processing. All previous approaches (eleventyComputed,
capturing page.url early with {% set %}) failed because the page
object is shared and mutated by parallel renders.

The transform approach works because outputPath is passed as a
function parameter (not read from a shared object) and IS correct
since files are written to the right location. The transform:

- Derives the OG slug from outputPath pattern matching
- Replaces __OG_IMAGE_PLACEHOLDER__ with the correct OG image URL
- Replaces __TWITTER_CARD_PLACEHOLDER__ with the correct card type
- Fixes og:url and canonical URL from outputPath
This commit is contained in:
Ricardo
2026-02-24 20:31:24 +01:00
parent ee068c7e5c
commit e56e2c67a5
2 changed files with 65 additions and 23 deletions

View File

@@ -1,14 +1,10 @@
<!DOCTYPE html>
<html lang="{{ site.locale | default('en') }}">
<head>
{# 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. #}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Eleventy">
@@ -25,11 +21,10 @@
{% set ogPhoto = ogPhoto[0] %}
{% endif %}
{% endif %}
<!-- debug:og _pageUrl={{ _pageUrl }} ogSlug={{ _ogSlug }} hasOg={{ _hasOg }} -->
<meta property="og:title" content="{{ ogTitle }}">
<meta property="og:site_name" content="{{ site.name }}">
<meta property="og:url" content="{{ site.url }}{{ _pageUrl }}">
<meta property="og:type" content="{% if _pageUrl == '/' %}website{% else %}article{% endif %}">
<meta property="og:url" content="{{ site.url }}{{ page.url }}">
<meta property="og:type" content="{% if page.url == '/' %}website{% else %}article{% endif %}">
<meta property="og:description" content="{{ ogDesc }}">
<meta name="description" content="{{ ogDesc }}">
{% if ogPhoto and ogPhoto != "" and (ogPhoto | length) > 10 %}
@@ -38,18 +33,16 @@
<meta property="og:image" content="{% if 'http' in image %}{{ image }}{% else %}{{ site.url }}{% if image[0] != '/' %}/{% endif %}{{ image }}{% endif %}">
{% elif contentImage and contentImage != "" and (contentImage | length) > 10 %}
<meta property="og:image" content="{% if 'http' in contentImage %}{{ contentImage }}{% else %}{{ site.url }}{% if contentImage[0] != '/' %}/{% endif %}{{ contentImage }}{% endif %}">
{% elif _hasOg %}
<meta property="og:image" content="{{ site.url }}/og/{{ _ogSlug }}.png">
{% else %}
<meta property="og:image" content="{{ site.url }}/images/og-default.png">
<meta property="og:image" content="__OG_IMAGE_PLACEHOLDER__">
{% endif %}
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:locale" content="{{ site.locale | default('en_US') }}">
{# 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) %}
<meta name="twitter:card" content="{% if hasImage %}summary_large_image{% else %}summary{% endif %}">
{% 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) %}
<meta name="twitter:card" content="{% if hasExplicitImage %}summary_large_image{% else %}__TWITTER_CARD_PLACEHOLDER__{% endif %}">
<meta name="twitter:title" content="{{ ogTitle }}">
<meta name="twitter:description" content="{{ ogDesc }}">
{% if ogPhoto and ogPhoto != "" and (ogPhoto | length) > 10 %}
@@ -58,8 +51,8 @@
<meta name="twitter:image" content="{% if 'http' in image %}{{ image }}{% else %}{{ site.url }}{% if image[0] != '/' %}/{% endif %}{{ image }}{% endif %}">
{% elif contentImage and contentImage != "" and (contentImage | length) > 10 %}
<meta name="twitter:image" content="{% if 'http' in contentImage %}{{ contentImage }}{% else %}{{ site.url }}{% if contentImage[0] != '/' %}/{% endif %}{{ contentImage }}{% endif %}">
{% elif _hasOg %}
<meta name="twitter:image" content="{{ site.url }}/og/{{ _ogSlug }}.png">
{% else %}
<meta name="twitter:image" content="__OG_IMAGE_PLACEHOLDER__">
{% endif %}
{# Favicon #}
@@ -102,17 +95,17 @@
[x-show*="loading"], button[\\@click*="fetch"], button[\\@click*="loadMore"] { display: none !important; }
</style>
</noscript>
<link rel="canonical" href="{{ site.url }}{{ _pageUrl }}">
<link rel="canonical" href="{{ site.url }}{{ page.url }}">
<link rel="alternate" type="application/rss+xml" href="/feed.xml" title="RSS Feed">
<link rel="alternate" type="application/json" href="/feed.json" title="JSON Feed">
{% if site.markdownAgents.enabled and _pageUrl and _pageUrl.startsWith('/articles/') %}
<link rel="alternate" type="text/markdown" href="{{ _pageUrl }}index.md" title="Markdown version">
{% if site.markdownAgents.enabled and page.url and page.url.startsWith('/articles/') %}
<link rel="alternate" type="text/markdown" href="{{ page.url }}index.md" title="Markdown version">
{% endif %}
<link rel="authorization_endpoint" href="{{ site.url }}/auth">
<link rel="token_endpoint" href="{{ site.url }}/auth/token">
<link rel="micropub" href="{{ site.url }}/micropub">
<link rel="microsub" href="{{ site.url }}/microsub">
<link rel="self" href="{{ site.url }}{{ _pageUrl }}">
<link rel="self" href="{{ site.url }}{{ page.url }}">
<link rel="hub" href="https://websubhub.com/hub">
<link rel="webmention" href="https://webmention.io/{{ site.webmentions.domain }}/webmention">
<link rel="pingback" href="https://webmention.io/{{ site.webmentions.domain }}/xmlrpc">
@@ -314,7 +307,7 @@
</header>
<main class="container py-8" data-pagefind-body>
{% 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 %}