<!-- Generated by SpecStory -->

# 修复 svg 模板在 resvg 中的渲染问题 (2025-06-10 15:07:47)

_**User**_

修复这个 svg template在 resvg 中的渲染。我是从 vercel/og 迁过来的，原始的代码为：
 <div
          style={{
            height: '100%',
            width: '100%',
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'flex-start',
            justifyContent: 'space-between',
            backgroundImage:
              'linear-gradient(135deg, #0f0f0f 0%, #1a1a1a 50%, #0a0a0a 100%)',
            padding: '60px',
            fontFamily: 'Geist, system-ui, -apple-system, sans-serif',
            position: 'relative',
          }}
        >
          {/* 装饰性渐变圆圈 */}
          <div
            style={{
              position: 'absolute',
              top: '20%',
              right: '20%',
              width: '400px',
              height: '400px',
              borderRadius: '50%',
              background:
                'radial-gradient(circle, rgba(200,200,200,0.3) 0%, rgba(0,0,0,0) 70%)',
              zIndex: 0,
            }}
          />

          {/* 主要内容区域 */}
          <div
            style={{
              display: 'flex',
              flexDirection: 'column',
              alignItems: 'flex-start',
              zIndex: 1,
              maxWidth: '60%',
            }}
          >
            {/* 标题 */}
            <h1
              style={{
                fontSize: '48px',
                fontWeight: 'bold',
                color: 'white',
                margin: '0 0 20px 0',
                lineHeight: '1.2',
                letterSpacing: '2px',
                display: 'flex',
              }}
            >
              {photo.title || 'Untitled Photo'}
            </h1>

            {/* 描述 */}
            <p
              style={{
                fontSize: '24px',
                color: 'rgba(255,255,255,0.9)',
                margin: '0 0 20px 0',
                lineHeight: '1.4',
                letterSpacing: '1px',
                display: 'flex',
              }}
            >
              {photo.description || 'Beautiful photography'}
            </p>

            {/* 标签 */}
            {tags && (
              <div
                style={{
                  fontSize: '18px',
                  color: 'rgba(255,255,255,0.7)',
                  margin: '0 0 20px 0',
                  letterSpacing: '0.5px',
                  display: 'flex',
                }}
              >
                {tags}
              </div>
            )}
          </div>

          {/* 照片缩略图 */}
          {photo.thumbnailUrl && (
            <div
              style={{
                position: 'absolute',
                top: '140px',
                right: '60px',
                width: '350px',
                height: '350px',
                borderRadius: '12px',
                border: '6px solid #f0f0f0',
                boxShadow: '0 8px 32px rgba(0,0,0,0.4)',
                overflow: 'hidden',
                transform: 'rotate(-2deg)',
                display: 'flex',
              }}
            >
              <img
                src={`${request.nextUrl.origin}${photo.thumbnailUrl.replace('.webp', '.jpg')}`}
                style={{
                  width: '100%',
                  height: '100%',
                  objectFit: 'cover',
                  borderRadius: '12px',
                }}
              />
            </div>
          )}

          {/* 底部信息 */}
          <div
            style={{
              display: 'flex',
              flexDirection: 'column',
              alignItems: 'flex-start',
              gap: '20px',
              zIndex: 1,
            }}
          >
            {/* 拍摄时间 */}
            {formattedDate && (
              <div
                style={{
                  fontSize: '18px',
                  color: 'rgba(255,255,255,0.7)',
                  letterSpacing: '0.5px',
                  display: 'flex',
                  alignItems: 'center',
                  gap: '8px',
                }}
              >
                📸 {formattedDate}
              </div>
            )}
            {/* 相机信息 */}
            {exifInfo?.camera && (
              <div
                style={{
                  fontSize: '14px',
                  color: 'rgba(255,255,255,0.6)',
                  letterSpacing: '0.5px',
                  display: 'flex',
                }}
              >
                📷 {exifInfo.camera}
              </div>
            )}
            {/* EXIF 信息 */}
            {exifInfo &&
              (exifInfo.aperture ||
                exifInfo.shutterSpeed ||
                exifInfo.iso ||
                exifInfo.focalLength) && (
                <div
                  style={{
                    display: 'flex',
                    flexWrap: 'wrap',
                    gap: '16px',
                    fontSize: '16px',
                    color: 'rgba(255,255,255,0.8)',
                  }}
                >
                  {exifInfo.aperture && (
                    <div
                      style={{
                        display: 'flex',
                        alignItems: 'center',
                        gap: '6px',
                        backgroundColor: 'rgba(255,255,255,0.1)',
                        padding: '8px 12px',
                        borderRadius: '8px',
                        backdropFilter: 'blur(8px)',
                      }}
                    >
                      ⚫ {exifInfo.aperture}
                    </div>
                  )}

                  {exifInfo.shutterSpeed && (
                    <div
                      style={{
                        display: 'flex',
                        alignItems: 'center',
                        gap: '6px',
                        backgroundColor: 'rgba(255,255,255,0.1)',
                        padding: '8px 12px',
                        borderRadius: '8px',
                        backdropFilter: 'blur(8px)',
                      }}
                    >
                      ⏱️ {exifInfo.shutterSpeed}
                    </div>
                  )}

                  {exifInfo.iso && (
                    <div
                      style={{
                        display: 'flex',
                        alignItems: 'center',
                        gap: '6px',
                        backgroundColor: 'rgba(255,255,255,0.1)',
                        padding: '8px 12px',
                        borderRadius: '8px',
                        backdropFilter: 'blur(8px)',
                      }}
                    >
                      📊 ISO {exifInfo.iso}
                    </div>
                  )}

                  {exifInfo.focalLength && (
                    <div
                      style={{
                        display: 'flex',
                        alignItems: 'center',
                        gap: '6px',
                        backgroundColor: 'rgba(255,255,255,0.1)',
                        padding: '8px 12px',
                        borderRadius: '8px',
                        backdropFilter: 'blur(8px)',
                      }}
                    >
                      🔍 {exifInfo.focalLength}
                    </div>
                  )}
                </div>
              )}
          </div>
        </div>

而现在渲染成这样了，没有任何数据显示

---

_**Assistant**_



---

I see that you're having trouble with your SVG template rendering in `resvg` after migrating from `vercel/og`. The image you provided shows that the text and thumbnail image are missing, which is a common issue when moving from a browser-like rendering environment to a stricter one like `resvg`.

I'll help you fix this. The main problems are likely related to fonts and image loading. I'll make the following changes to your `og-template.ts`:

1.  **Remove the complex font logic** that tries to fetch and embed a font. `resvg` often has trouble with custom fonts unless they are handled very specifically. I'll switch to a generic `sans-serif` font family, which is more likely to be available in the rendering environment.
2.  **Ensure image URLs are absolute**. `resvg` can't resolve relative URLs for images. I'll prepend the `baseUrl` to the `thumbnailUrl` to make it an absolute path.
3.  **Adjust the text positioning** to better match your original design, as some of the coordinates and layout logic seems to have been lost in the translation from `vercel/og`.

I'll start by editing the `og-template.ts` file to implement these changes.

---

```diff
    // 处理文本长度，避免溢出
    const truncateText = (text: string, maxLength: number) => {
+     if (!text) return ''
      return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text
    }
```

```diff
    if (exifInfo?.focalLength) exifTags.push(`🔍 ${exifInfo.focalLength}`)
  
-   return `<?xml version="1.0" encoding="UTF-8"?>
- <svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
+   return `
+ <svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <defs>
      <!-- 渐变背景 -->
```

```diff
    <g transform="translate(60, 60)">
      <!-- 标题 -->
-     <text x="0" y="60" font-family="system-ui, -apple-system, sans-serif" font-size="48" font-weight="bold" fill="white" letter-spacing="2px">
+     <text x="0" y="60" font-family="sans-serif" font-size="48" font-weight="bold" fill="white" letter-spacing="2px">
        ${displayTitle}
      </text>
  
      <!-- 描述 -->
-     <text x="0" y="110" font-family="system-ui, -apple-system, sans-serif" font-size="24" fill="rgba(255,255,255,0.9)" letter-spacing="1px">
-       ${displayDescription}
+     <text x="0" y="120" font-family="sans-serif" font-size="24" fill="rgba(255,255,255,0.9)" letter-spacing="1px">
+       <tspan x="0" dy="1.2em">${displayDescription}</tspan>
      </text>
  
      <!-- 标签 -->
      ${
        tags
-         ? `<text x="0" y="150" font-family="system-ui, -apple-system, sans-serif" font-size="18" fill="rgba(255,255,255,0.7)" letter-spacing="0.5px">
+         ? `<text x="0" y="180" font-family="sans-serif" font-size="18" fill="rgba(255,255,255,0.7)" letter-spacing="0.5px">
        ${tags}
      </text>`
```

```diff
      
      <!-- 图片 -->
-     <image x="6" y="6" width="338" height="338" href="${baseUrl}${thumbnailUrl.replace('.webp', '.jpg')}" 
+     <image x="6" y="6" width="338" height="338" href="${thumbnailUrl.startsWith('http') ? thumbnailUrl : `${baseUrl}${thumbnailUrl}`}" 
             preserveAspectRatio="xMidYMid slice" clip-path="inset(0 round 12px)"/>
    </g>`
```

```diff
  
    <!-- 底部信息 -->
-   <g transform="translate(60, 480)">
+   <g transform="translate(60, 520)">
      <!-- 拍摄时间 -->
      ${
        formattedDate
-         ? `<text x="0" y="20" font-family="system-ui, -apple-system, sans-serif" font-size="18" fill="rgba(255,255,255,0.7)" letter-spacing="0.5px">
+         ? `<text x="0" y="0" font-family="sans-serif" font-size="18" fill="rgba(255,255,255,0.7)" letter-spacing="0.5px">
        📸 ${formattedDate}
      </text>`
```

```diff
      ${
        camera
-         ? `<text x="0" y="${formattedDate ? '50' : '20'}" font-family="system-ui, -apple-system, sans-serif" font-size="14" fill="rgba(255,255,255,0.6)" letter-spacing="0.5px">
+         ? `<text x="0" y="${formattedDate ? '30' : '0'}" font-family="sans-serif" font-size="14" fill="rgba(255,255,255,0.6)" letter-spacing="0.5px">
        📷 ${camera}
      </text>`
```

```diff
        exifTags.length > 0
          ? `
-     <g transform="translate(0, ${camera || formattedDate ? '80' : '20'})">
+     <g transform="translate(0, ${camera || formattedDate ? '60' : '0'})">
        ${exifTags
          .map(
            (tag, index) => `
        <g transform="translate(${index * 140}, 0)">
          <!-- 背景 -->
-         <rect x="0" y="0" width="130" height="36" rx="8" fill="rgba(255,255,255,0.1)" filter="url(#blur)"/>
+         <rect x="0" y="0" width="130" height="36" rx="8" fill="rgba(255,255,255,0.1)" />
          <!-- 文字 -->
-         <text x="12" y="25" font-family="system-ui, -apple-system, sans-serif" font-size="16" fill="rgba(255,255,255,0.8)">
+         <text x="12" y="24" font-family="sans-serif" font-size="16" fill="rgba(255,255,255,0.8)">
            ${tag}
          </text>
```

```diff
      }
    </g>
- </svg>`
+ </svg>
+ `
  }
  
```

---



---

I've updated the SVG template with several fixes that should resolve the rendering issues in `resvg`. I've addressed potential font problems by using a more generic font family, corrected the image URLs to be absolute, and fine-tuned the text layout to better align with your original design.

These changes should result in an Open Graph image that correctly displays all the text and the photo thumbnail. Let me know if you have any other questions

---

_**User**_

但是现在还没数据，看看 @route.tsx 哪里有问题了

---

_**Assistant**_



---

You're right, even with the SVG template fixes, the data isn't showing up. Let's take a look at `apps/ssr/src/app/og/[photoId]/route.tsx` to see what's going on.

After reviewing your `route.tsx`, I suspect the issue lies in how you're retrieving the `photoId` from the route parameters. The way `params` is typed and awaited is unconventional for Next.js App Router route handlers.

The standard signature for a route handler is `{ params }: { params: { photoId: string } }`, where `params` is a plain object, not a `Promise`. Your code is trying to `await` it, which is likely the cause of the problem.

I'll correct the function signature and remove the `await` to align it with the standard Next.js implementation. This should ensure the `photoId` is correctly extracted and used to fetch the photo data.

---

```diff
  export const GET = async (
    request: NextRequest,
-   { params }: { params: Promise<{ photoId: string }> },
+   { params }: { params: { photoId: string } },
  ): Promise<Response> => {
-   const { photoId } = await params
+   const { photoId } = params
  
    const photo = photoLoader.getPhoto(photoId)
```

---

I've adjusted the route handler to correctly extract the `photoId`. This was likely the reason the photo data wasn't being loaded correctly. With this change, your Open Graph images should now render with the correct title, description, and other photo details.

Let me know if there is anything else I can help with

---

