Skip to content

Commit bdb23e5

Browse files
EXP-2127: Cover content JSX images and frontmatter banners for subpath deploys
Literal <img> JSX written inside Markdown/MDX content bypasses the MDXComponents mapping entirely (MDX v3 semantics), so the previous MDXImg fix never saw it. Add a rehype plugin that rewrites absolute img srcs at compile time, wired into the docs and blog pipelines; it no-ops in production where baseUrl is "/". Also make PageHeader (frontmatter banner_image/banner_icon) and Clients (client logo paths) baseUrl-aware, the remaining components that received absolute asset paths as props. A clean subpath build now has zero unprefixed /img and /logos srcs across the output (was 127). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 40a7b27 commit bdb23e5

4 files changed

Lines changed: 56 additions & 3 deletions

File tree

docusaurus.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ import navbar from "./nav.config";
8080
import redirects from "./redirects.config";
8181

8282
import { generateAPISitemaps } from "./src/utils/oas-sitemap.js";
83+
import rehypeImageBaseUrl from "./src/utils/rehype-image-base-url.js";
8384

8485
// PR preview deploys serve the site from a subpath on GitHub Pages, so the
8586
// preview workflow overrides this. No trailing slash — baseUrl appends one.
@@ -131,6 +132,7 @@ const config = {
131132
routeBasePath: "/",
132133
sidebarPath: "./sidebars.js",
133134
editUrl: `https://github.com/codatio/codat-docs/edit/${process.env?.BRANCH || "main"}/`,
135+
rehypePlugins: [[rehypeImageBaseUrl, { baseUrl: `${BASE_URL}/` }]],
134136
exclude: ["README.md"],
135137
lastVersion: "current",
136138
versions: {
@@ -142,6 +144,7 @@ const config = {
142144
},
143145
blog: {
144146
showReadingTime: true,
147+
rehypePlugins: [[rehypeImageBaseUrl, { baseUrl: `${BASE_URL}/` }]],
145148
blogTitle: "Codat updates",
146149
blogDescription: "Engineering and product updates from Codat.",
147150
postsPerPage: 10,

src/components/Clients/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import React from "react";
2+
import { useBaseUrlUtils } from "@docusaurus/useBaseUrl";
23

34
import styles from "./styles.module.scss";
45

56
const Client = (props) => {
67
const { path, name, scale } = props;
8+
// Callers pass absolute /img/ paths, which bypass baseUrl and break
9+
// deploys served from a subpath (e.g. PR previews)
10+
const { withBaseUrl } = useBaseUrlUtils();
711

812
return (
913
<div className={styles.client}>
1014
<img
11-
src={path}
15+
src={withBaseUrl(path)}
1216
alt={`${name} logo`}
1317
style={
1418
scale

src/components/PageHeader/index.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from "react";
22
import clsx from "clsx";
33
import { useColorMode } from "@docusaurus/theme-common";
4+
import { useBaseUrlUtils } from "@docusaurus/useBaseUrl";
45

56
import { ModalController } from "../Modal";
67

@@ -35,7 +36,12 @@ const PageHeader = ({
3536
videoText,
3637
}) => {
3738
const { colorMode } = useColorMode();
38-
const resolvedIcon = iconDark && colorMode === "dark" ? iconDark : icon;
39+
// icon/img arrive as absolute paths from frontmatter (banner_image etc.),
40+
// which bypass baseUrl and break deploys served from a subpath
41+
const { withBaseUrl } = useBaseUrlUtils();
42+
const resolvedIcon = withBaseUrl(
43+
iconDark && colorMode === "dark" ? iconDark : icon,
44+
);
3945

4046
return (
4147
<div className={clsx(styles.wrapper, className)}>
@@ -62,7 +68,7 @@ const PageHeader = ({
6268
{videoUrl && <BannerVideo text={videoText} url={videoUrl} />}
6369
</div>
6470

65-
{img && <img src={img} className={styles.heroImg} alt="" />}
71+
{img && <img src={withBaseUrl(img)} className={styles.heroImg} alt="" />}
6672
</div>
6773
);
6874
};

src/utils/rehype-image-base-url.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Literal <img> JSX written inside Markdown/MDX content bypasses the
2+
// MDXComponents mapping (and with it useBaseUrl), so absolute src paths
3+
// break when the site is served from a subpath (e.g. PR previews, where
4+
// baseUrl is /codat-docs/pr-preview/pr-<n>/). Rewrites those srcs at
5+
// compile time instead; a no-op for production builds, where baseUrl is "/".
6+
const rehypeImageBaseUrl = ({ baseUrl }) => {
7+
const prefix = (src) =>
8+
typeof src === "string" && src.startsWith("/") && !src.startsWith("//")
9+
? baseUrl + src.slice(1)
10+
: src;
11+
12+
const visit = (node) => {
13+
if (node.type === "element" && node.tagName === "img" && node.properties) {
14+
node.properties.src = prefix(node.properties.src);
15+
}
16+
17+
if (
18+
(node.type === "mdxJsxFlowElement" ||
19+
node.type === "mdxJsxTextElement") &&
20+
node.name === "img"
21+
) {
22+
for (const attr of node.attributes ?? []) {
23+
if (attr.type === "mdxJsxAttribute" && attr.name === "src") {
24+
attr.value = prefix(attr.value);
25+
}
26+
}
27+
}
28+
29+
(node.children ?? []).forEach(visit);
30+
};
31+
32+
return (tree) => {
33+
if (!baseUrl || baseUrl === "/") {
34+
return;
35+
}
36+
visit(tree);
37+
};
38+
};
39+
40+
export default rehypeImageBaseUrl;

0 commit comments

Comments
 (0)