Skip to content

Commit 2429bf4

Browse files
authored
Fix images (#6)
* Support runtime domain for MDX images and update related documentation * Enhance MdxImage component to support non-string src types and improve image source resolution * Support direct serving of images from the /content route with path validation
1 parent 298cba3 commit 2429bf4

9 files changed

Lines changed: 109 additions & 7 deletions

File tree

app/content/[...path]/route.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { NextResponse } from 'next/server';
2+
import { readFile } from 'fs/promises';
3+
import path from 'path';
4+
5+
export const runtime = 'nodejs';
6+
7+
const CONTENT_ROOT = path.resolve(process.cwd(), 'content');
8+
const MIME_TYPES: Record<string, string> = {
9+
'.png': 'image/png',
10+
'.jpg': 'image/jpeg',
11+
'.jpeg': 'image/jpeg',
12+
'.webp': 'image/webp',
13+
'.gif': 'image/gif',
14+
'.svg': 'image/svg+xml',
15+
};
16+
17+
const isSafePath = (filePath: string) =>
18+
filePath === CONTENT_ROOT || filePath.startsWith(`${CONTENT_ROOT}${path.sep}`);
19+
20+
export async function GET(
21+
request: Request,
22+
{ params }: { params: { path?: string[] } },
23+
) {
24+
const segments = params.path ?? [];
25+
const filePath = path.resolve(CONTENT_ROOT, ...segments);
26+
if (!isSafePath(filePath)) {
27+
return new NextResponse('Not Found', { status: 404 });
28+
}
29+
30+
const extension = path.extname(filePath).toLowerCase();
31+
const contentType = MIME_TYPES[extension];
32+
if (!contentType) {
33+
return new NextResponse('Not Found', { status: 404 });
34+
}
35+
36+
try {
37+
const data = await readFile(filePath);
38+
return new NextResponse(data, {
39+
status: 200,
40+
headers: {
41+
'Content-Type': contentType,
42+
'Cache-Control': 'public, max-age=31536000, immutable',
43+
},
44+
});
45+
} catch {
46+
return new NextResponse('Not Found', { status: 404 });
47+
}
48+
}

components/mdx/mdx-image.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import { ImageZoom } from 'fumadocs-ui/components/image-zoom';
4-
import { useMemo } from 'react';
4+
import { useEffect, useMemo, useRef, useState } from 'react';
55

66
const CDN_PATTERN = /^https?:\/\/images\.sealos\.run/;
77
const APP_URL_PLACEHOLDER = '__APP_URL__';
@@ -56,15 +56,35 @@ const resolveImageSrc = (src: MdxImageSrc) => {
5656
};
5757

5858
export function MdxImage(props: React.ImgHTMLAttributes<HTMLImageElement>) {
59-
const { className, src, ...rest } = props;
59+
const { className, src, onError, ...rest } = props;
6060
const resolvedSrc = useMemo(() => resolveImageSrc(src), [src]);
61+
const [currentSrc, setCurrentSrc] = useState<MdxImageSrc>(resolvedSrc);
62+
const [didFallback, setDidFallback] = useState(false);
63+
const originalSrcRef = useRef<MdxImageSrc>(src);
64+
const resolvedSrcRef = useRef<MdxImageSrc>(resolvedSrc);
6165
const mergedClassName = ['rounded-xl', className].filter(Boolean).join(' ');
6266

67+
useEffect(() => {
68+
originalSrcRef.current = src;
69+
resolvedSrcRef.current = resolvedSrc;
70+
setCurrentSrc(resolvedSrc);
71+
setDidFallback(false);
72+
}, [src, resolvedSrc]);
73+
74+
const handleError: React.ReactEventHandler<HTMLImageElement> = (event) => {
75+
onError?.(event);
76+
if (didFallback) return;
77+
if (resolvedSrcRef.current === originalSrcRef.current) return;
78+
setDidFallback(true);
79+
setCurrentSrc(originalSrcRef.current);
80+
};
81+
6382
return (
6483
<ImageZoom
6584
{...(rest as any)}
66-
src={resolvedSrc}
85+
src={currentSrc}
6786
className={mergedClassName}
87+
onError={handleError}
6888
/>
6989
);
7090
}

helloagents/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- 新增 Sealos Hub 镜像上传文档
1212
- 新增 PR 预览环境工作流
1313
- 支持 MDX 图片运行时域名
14+
- 支持 content 图片直出
1415

1516
### 变更
1617
- 初始化知识库(helloagents)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# 任务清单: content 图片直出
2+
3+
目录: `helloagents/plan/202601202306_content-image-route/`
4+
5+
---
6+
7+
## 1. 服务端路由
8+
- [] 1.1 在 `app/content/[...path]/route.ts` 中实现 content 图片直出(仅允许图片扩展名,防止路径穿越)
9+
- [] 1.2 更新 `middleware.ts` 放行 `/content/` 路由避免被 i18n 重写
10+
11+
## 2. 构建脚本
12+
- [] 2.1 更新 `scripts/replace-image-paths.sh``./images` 替换为 `/content/.../images`
13+
14+
## 3. 文档同步
15+
- [] 3.1 更新 `helloagents/wiki/modules/app.md` 记录 content 图片直出规范
16+
- [] 3.2 更新 `helloagents/CHANGELOG.md` 记录改动
17+
- [] 3.3 更新 `helloagents/history/index.md` 增加历史索引
18+
19+
## 4. 校验
20+
- [] 4.1 访问本地 `/content/...` 图片地址验证返回 `image/*`

helloagents/history/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
| 时间戳 | 功能名称 | 类型 | 状态 | 方案包路径 |
1010
|--------|----------|------|------|------------|
11+
| 202601202306 | content 图片直出 | 功能 | ✅已完成 | [链接](2026-01/202601202306_content-image-route/) |
1112
| 202601202148 | MDX 图片运行时域名 | 功能 | ✅已完成 | [链接](2026-01/202601202148_mdx-image-runtime-base/) |
1213
| 202601201758 | Sealos Hub 镜像上传文档 | 文档 | ✅已完成 | [链接](2026-01/202601201758_sealos-hub-image-upload-doc/) |
1314
| 202601201726 | AI Proxy 渠道配置文档 | 文档 | ✅已完成 | [链接](2026-01/202601201726_aiproxy-channel-doc/) |
@@ -21,6 +22,7 @@
2122

2223
### 2026-01
2324

25+
- [202601202306_content-image-route](2026-01/202601202306_content-image-route/) - content 图片直出
2426
- [202601202148_mdx-image-runtime-base](2026-01/202601202148_mdx-image-runtime-base/) - MDX 图片运行时域名
2527
- [202601201927_pr-preview-workflow](2026-01/202601201927_pr-preview-workflow/) - PR 预览环境工作流
2628
- [202601201758_sealos-hub-image-upload-doc](2026-01/202601201758_sealos-hub-image-upload-doc/) - Sealos Hub 镜像上传文档

helloagents/wiki/modules/app.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@
2828
- 图片可随部署域名切换
2929
- 保持 ImageZoom 交互体验
3030

31+
### 需求: content 图片直出
32+
**模块:** app
33+
提供 `/content/*` 路由直接读取仓库 `content/` 目录中的图片资源。
34+
35+
#### 场景: 文档图片直出
36+
图片请求可通过 `/content/...` 访问到 `content/` 内的图片文件。
37+
- 限制仅允许图片扩展名
38+
- 防止路径穿越
39+
3140
## API接口
3241
暂无
3342

@@ -42,3 +51,4 @@
4251

4352
## 变更历史
4453
- [202601202148_mdx-image-runtime-base](../../history/2026-01/202601202148_mdx-image-runtime-base/) - 支持 MDX 图片运行时域名
54+
- [202601202306_content-image-route](../../history/2026-01/202601202306_content-image-route/) - 支持 content 图片直出

helloagents/wiki/modules/components.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
在渲染时替换 MDX 图片地址为当前部署域名。
2828
- 兼容现有 CDN 图片地址
2929
- 兼容非字符串 src(如静态导入)
30+
- 替换失败时回退至原始地址
3031
- 保持图片圆角样式
3132

3233
## API接口

middleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,6 @@ export async function middleware(request: NextRequest, event: NextFetchEvent) {
5353

5454
export const config = {
5555
matcher: [
56-
'/((?!api|_next/static|_next/image|images/|icons/|favicon/|favicon.ico|logo.svg|Deploy-on-Sealos.svg|sitemap.xml|llms.txt|rss.xml|ai-faqs\..*\.json).*)/',
56+
'/((?!api|_next/static|_next/image|content/|images/|icons/|favicon/|favicon.ico|logo.svg|Deploy-on-Sealos.svg|sitemap.xml|llms.txt|rss.xml|ai-faqs\..*\.json).*)/',
5757
],
5858
};

scripts/replace-image-paths.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ find content -type f \( -name "*.mdx" -o -name "*.md" \) | while read -r file; d
66
# Get the directory path relative to project root
77
dir=$(dirname "$file")
88

9-
# Replace "](./images" with CDN URL + absolute path
10-
sed -i "s#](\./images#](https://images.sealos.run/gh/labring/sealos.io@main/${dir}/images#g" "$file"
9+
# Replace "](./images" with local content path for in-app serving
10+
sed -i "s#](\./images#](/content/${dir}/images#g" "$file"
1111

1212
echo "Processed: $file"
1313
done
1414

15-
echo "Image path replacement completed"
15+
echo "Image path replacement completed"

0 commit comments

Comments
 (0)