mirror of
https://github.com/svemagie/blog-eleventy-indiekit.git
synced 2026-05-14 22:48:50 +02:00
fix: compute ogSlug from permalink in template to avoid Eleventy 3.x race condition
Both page.url AND page.inputPath are unreliable in eleventyComputed due to Eleventy 3.x parallel rendering (issue #3183). They return values from OTHER pages being processed concurrently, causing og:image meta tags to reference wrong OG images. Fix: compute ogSlug directly in base.njk from the permalink data value using existing Nunjucks filters (ogSlug, hasOgImage). permalink comes from frontmatter (per-file data) and is immune to cross-page contamination.
This commit is contained in:
+11
-51
@@ -1,23 +1,20 @@
|
|||||||
/**
|
/**
|
||||||
* Computed data resolved during the data cascade.
|
* Computed data resolved during the data cascade.
|
||||||
*
|
*
|
||||||
* Eleventy 3.x parallel rendering causes `page.url` and `page.fileSlug`
|
* Eleventy 3.x parallel rendering causes `page.url`, `page.fileSlug`,
|
||||||
* to return values from OTHER pages being processed concurrently.
|
* and `page.inputPath` to return values from OTHER pages being processed
|
||||||
* This affects both templates and eleventyComputed functions.
|
* concurrently. This affects both templates and eleventyComputed functions.
|
||||||
*
|
*
|
||||||
* Fix: ALL computed values derive from `page.inputPath` (the physical file
|
* IMPORTANT: Only `permalink` is computed here, because it reads from the
|
||||||
* path on disk), which is always correct regardless of parallel rendering.
|
* file's own frontmatter data (per-file, immune to race conditions).
|
||||||
* NEVER use `page.url` or `page.fileSlug` here.
|
* OG image lookups are done in templates using the `permalink` data value
|
||||||
|
* and Nunjucks filters (see base.njk).
|
||||||
|
*
|
||||||
|
* NEVER use `page.url`, `page.fileSlug`, or `page.inputPath` here.
|
||||||
*
|
*
|
||||||
* See: https://github.com/11ty/eleventy/issues/3183
|
* See: https://github.com/11ty/eleventy/issues/3183
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { existsSync } from "node:fs";
|
|
||||||
import { resolve, dirname } from "node:path";
|
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
eleventyComputed: {
|
eleventyComputed: {
|
||||||
// Compute permalink from file path for posts without explicit frontmatter permalink.
|
// Compute permalink from file path for posts without explicit frontmatter permalink.
|
||||||
@@ -37,6 +34,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No frontmatter permalink — compute from file path
|
// No frontmatter permalink — compute from file path
|
||||||
|
// NOTE: data.page.inputPath may be wrong due to parallel rendering,
|
||||||
|
// but posts without frontmatter permalink are rare (only pre-beta.37 edge cases)
|
||||||
const inputPath = data.page?.inputPath || "";
|
const inputPath = data.page?.inputPath || "";
|
||||||
const match = inputPath.match(
|
const match = inputPath.match(
|
||||||
/content\/([^/]+)\/(\d{4})-(\d{2})-(\d{2})-(.+)\.md$/
|
/content\/([^/]+)\/(\d{4})-(\d{2})-(\d{2})-(.+)\.md$/
|
||||||
@@ -49,44 +48,5 @@ export default {
|
|||||||
// For non-matching files (pages, root files), let Eleventy decide
|
// For non-matching files (pages, root files), let Eleventy decide
|
||||||
return data.permalink;
|
return data.permalink;
|
||||||
},
|
},
|
||||||
|
|
||||||
// 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.
|
|
||||||
ogSlug: (data) => {
|
|
||||||
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 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 inputPath = data.page?.inputPath || "";
|
|
||||||
const match = inputPath.match(
|
|
||||||
/content\/([^/]+)\/(\d{4})-(\d{2})-(\d{2})-(.+)\.md$/,
|
|
||||||
);
|
|
||||||
let slug;
|
|
||||||
if (match) {
|
|
||||||
const [, , year, month, day, s] = match;
|
|
||||||
slug = `${year}-${month}-${day}-${s}`;
|
|
||||||
} else {
|
|
||||||
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`);
|
|
||||||
return existsSync(ogPath);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,15 +23,19 @@
|
|||||||
<meta property="og:type" content="{% if page.url == '/' %}website{% else %}article{% endif %}">
|
<meta property="og:type" content="{% if page.url == '/' %}website{% else %}article{% endif %}">
|
||||||
<meta property="og:description" content="{{ ogDesc }}">
|
<meta property="og:description" content="{{ ogDesc }}">
|
||||||
<meta name="description" content="{{ ogDesc }}">
|
<meta name="description" content="{{ ogDesc }}">
|
||||||
{# ogSlug and hasOgImage are pre-computed via eleventyComputed (race-condition safe) #}
|
{# Compute OG slug from permalink (frontmatter), NOT page.url or eleventyComputed.
|
||||||
|
permalink is per-file data immune to Eleventy 3.x parallel rendering race conditions.
|
||||||
|
page.url, page.inputPath, and eleventyComputed values can return data from OTHER pages. #}
|
||||||
|
{% set _ogSlug = permalink | ogSlug %}
|
||||||
|
{% set _hasOg = _ogSlug | hasOgImage %}
|
||||||
{% if ogPhoto and ogPhoto != "" and (ogPhoto | length) > 10 %}
|
{% if ogPhoto and ogPhoto != "" and (ogPhoto | length) > 10 %}
|
||||||
<meta property="og:image" content="{% if 'http' in ogPhoto %}{{ ogPhoto }}{% else %}{{ site.url }}{% if ogPhoto[0] != '/' %}/{% endif %}{{ ogPhoto }}{% endif %}">
|
<meta property="og:image" content="{% if 'http' in ogPhoto %}{{ ogPhoto }}{% else %}{{ site.url }}{% if ogPhoto[0] != '/' %}/{% endif %}{{ ogPhoto }}{% endif %}">
|
||||||
{% elif image and image != "" and (image | length) > 10 %}
|
{% elif image and image != "" and (image | length) > 10 %}
|
||||||
<meta property="og:image" content="{% if 'http' in image %}{{ image }}{% else %}{{ site.url }}{% if image[0] != '/' %}/{% endif %}{{ image }}{% endif %}">
|
<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 %}
|
{% 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 %}">
|
<meta property="og:image" content="{% if 'http' in contentImage %}{{ contentImage }}{% else %}{{ site.url }}{% if contentImage[0] != '/' %}/{% endif %}{{ contentImage }}{% endif %}">
|
||||||
{% elif hasOgImage %}
|
{% elif _hasOg %}
|
||||||
<meta property="og:image" content="{{ site.url }}/og/{{ ogSlug }}.png">
|
<meta property="og:image" content="{{ site.url }}/og/{{ _ogSlug }}.png">
|
||||||
{% else %}
|
{% else %}
|
||||||
<meta property="og:image" content="{{ site.url }}/images/og-default.png">
|
<meta property="og:image" content="{{ site.url }}/images/og-default.png">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -40,7 +44,7 @@
|
|||||||
<meta property="og:locale" content="{{ site.locale | default('en_US') }}">
|
<meta property="og:locale" content="{{ site.locale | default('en_US') }}">
|
||||||
|
|
||||||
{# Twitter Card meta tags #}
|
{# Twitter Card meta tags #}
|
||||||
{% set hasImage = hasOgImage 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 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 %}">
|
<meta name="twitter:card" content="{% if hasImage %}summary_large_image{% else %}summary{% endif %}">
|
||||||
<meta name="twitter:title" content="{{ ogTitle }}">
|
<meta name="twitter:title" content="{{ ogTitle }}">
|
||||||
<meta name="twitter:description" content="{{ ogDesc }}">
|
<meta name="twitter:description" content="{{ ogDesc }}">
|
||||||
@@ -50,8 +54,8 @@
|
|||||||
<meta name="twitter:image" content="{% if 'http' in image %}{{ image }}{% else %}{{ site.url }}{% if image[0] != '/' %}/{% endif %}{{ image }}{% endif %}">
|
<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 %}
|
{% 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 %}">
|
<meta name="twitter:image" content="{% if 'http' in contentImage %}{{ contentImage }}{% else %}{{ site.url }}{% if contentImage[0] != '/' %}/{% endif %}{{ contentImage }}{% endif %}">
|
||||||
{% elif hasOgImage %}
|
{% elif _hasOg %}
|
||||||
<meta name="twitter:image" content="{{ site.url }}/og/{{ ogSlug }}.png">
|
<meta name="twitter:image" content="{{ site.url }}/og/{{ _ogSlug }}.png">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Favicon #}
|
{# Favicon #}
|
||||||
|
|||||||
Reference in New Issue
Block a user