Skip to content

Commit f4a515d

Browse files
alexeyvclaude
andcommitted
fix: prepend base path to absolute URLs for subdirectory deployments
Add rehype-base-images plugin for image src attributes and update rehype-markdown-links to prepend base path to absolute hrefs. Fixes broken links/images when deploying to GitHub Pages project sites (e.g., user.github.io/repo/). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 1578561 commit f4a515d

File tree

3 files changed

+71
-3
lines changed

3 files changed

+71
-3
lines changed

website/astro.config.mjs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { defineConfig } from 'astro/config';
33
import starlight from '@astrojs/starlight';
44
import sitemap from '@astrojs/sitemap';
55
import rehypeMarkdownLinks from './src/rehype-markdown-links.js';
6+
import rehypeBaseImages from './src/rehype-base-images.js';
67
import { getSiteUrl } from './src/lib/site-url.js';
78

89
const siteUrl = getSiteUrl();
@@ -28,7 +29,10 @@ export default defineConfig({
2829
},
2930

3031
markdown: {
31-
rehypePlugins: [rehypeMarkdownLinks],
32+
rehypePlugins: [
33+
[rehypeMarkdownLinks, { base: basePath }],
34+
[rehypeBaseImages, { base: basePath }],
35+
],
3236
},
3337

3438
integrations: [

website/src/rehype-base-images.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Rehype plugin to prepend base path to absolute image URLs
3+
*
4+
* Transforms:
5+
* /img/foo.png → /BMAD-METHOD/img/foo.png (when base is /BMAD-METHOD/)
6+
* /img/foo.png → /img/foo.png (when base is /)
7+
*
8+
* Only affects absolute paths (/) - relative paths and external URLs are unchanged.
9+
*/
10+
11+
import { visit } from 'unist-util-visit';
12+
13+
/**
14+
* Create a rehype plugin that prepends the base path to absolute image URLs.
15+
*
16+
* @param {Object} options - Plugin options
17+
* @param {string} options.base - The base path to prepend (e.g., '/BMAD-METHOD/')
18+
* @returns {function} A HAST tree transformer
19+
*/
20+
export default function rehypeBaseImages(options = {}) {
21+
const base = options.base || '/';
22+
23+
// Normalize base: ensure it ends with / and doesn't have double slashes
24+
const normalizedBase = base === '/' ? '/' : base.endsWith('/') ? base : base + '/';
25+
26+
return (tree) => {
27+
visit(tree, 'element', (node) => {
28+
// Process img tags with src attribute
29+
if (node.tagName === 'img' && node.properties?.src) {
30+
const src = node.properties.src;
31+
32+
if (typeof src !== 'string') {
33+
return;
34+
}
35+
36+
// Only transform absolute paths starting with / (but not //)
37+
if (src.startsWith('/') && !src.startsWith('//')) {
38+
// Don't transform if already has the base path
39+
if (normalizedBase !== '/' && src.startsWith(normalizedBase)) {
40+
return;
41+
}
42+
43+
// Prepend base path (remove leading / from src since base ends with /)
44+
node.properties.src = normalizedBase + src.slice(1);
45+
}
46+
}
47+
});
48+
};
49+
}

website/src/rehype-markdown-links.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
* ./path/index.md → ./path/ (index.md becomes directory root)
77
* ../path/file.md#anchor → ../path/file/#anchor
88
* ./file.md?query=param → ./file/?query=param
9-
* /docs/absolute/path/file.md → /absolute/path/file/
9+
* /docs/absolute/path/file.md → {base}/absolute/path/file/
1010
*
1111
* For absolute paths starting with /docs/, the /docs prefix is stripped
1212
* since the Astro site serves content from the docs directory as the root.
13+
* The base path is prepended to absolute paths for subdirectory deployments.
1314
*
1415
* Affects relative links (./, ../) and absolute paths (/) - external links are unchanged
1516
*/
@@ -21,9 +22,15 @@ import { visit } from 'unist-util-visit';
2122
*
2223
* The returned transformer walks the HTML tree and rewrites anchor `href` values that are relative paths (./, ../) or absolute paths (/) pointing to `.md` files. It preserves query strings and hash anchors, rewrites `.../index.md` to the directory root path (`.../`), and rewrites other `.md` file paths by removing the `.md` extension and ensuring a trailing slash. External links (http://, https://) and non-.md links are left unchanged.
2324
*
25+
* @param {Object} options - Plugin options
26+
* @param {string} options.base - The base path to prepend to absolute URLs (e.g., '/BMAD-METHOD/')
2427
* @returns {function} A HAST tree transformer that mutates `a` element `href` properties as described.
2528
*/
26-
export default function rehypeMarkdownLinks() {
29+
export default function rehypeMarkdownLinks(options = {}) {
30+
const base = options.base || '/';
31+
// Normalize base: ensure it ends with / and doesn't have double slashes
32+
const normalizedBase = base === '/' ? '' : base.endsWith('/') ? base.slice(0, -1) : base;
33+
2734
return (tree) => {
2835
visit(tree, 'element', (node) => {
2936
// Only process anchor tags with href
@@ -82,6 +89,9 @@ export default function rehypeMarkdownLinks() {
8289
}
8390
}
8491

92+
// Track if this was an absolute path (for base path prepending)
93+
const isAbsolute = urlPath.startsWith('/');
94+
8595
// Strip /docs/ prefix from absolute paths (repo-relative → site-relative)
8696
if (urlPath.startsWith('/docs/')) {
8797
urlPath = urlPath.slice(5); // Remove '/docs' prefix, keeping the leading /
@@ -95,6 +105,11 @@ export default function rehypeMarkdownLinks() {
95105
urlPath = urlPath.replace(/\.md$/, '/');
96106
}
97107

108+
// Prepend base path to absolute URLs for subdirectory deployments
109+
if (isAbsolute && normalizedBase) {
110+
urlPath = normalizedBase + urlPath;
111+
}
112+
98113
// Reconstruct the href
99114
node.properties.href = urlPath + query + anchor;
100115
});

0 commit comments

Comments
 (0)