[PR #236] [MERGED] fix: handle SVGs in image optimization to match Next.js behavior #409

Closed
opened 2026-05-06 12:39:40 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/236
Author: @southpolesteve
Created: 3/3/2026
Status: Merged
Merged: 3/3/2026
Merged by: @southpolesteve

Base: mainHead: fix/svg-image-support


📝 Commits (1)

  • fcbf801 fix: handle SVGs in image optimization to match Next.js behavior (#205)

📊 Changes

7 files changed (+297 additions, -25 deletions)

View changed files

📝 packages/vinext/src/config/next-config.ts (+6 -0)
📝 packages/vinext/src/deploy.ts (+14 -1)
📝 packages/vinext/src/index.ts (+43 -0)
📝 packages/vinext/src/server/image-optimization.ts (+43 -12)
📝 packages/vinext/src/server/prod-server.ts (+26 -9)
📝 packages/vinext/src/shims/image.tsx (+16 -3)
📝 tests/shims.test.ts (+149 -0)

📄 Description

Summary

Fixes #205. SVGs passed to /_vinext/image now work correctly, matching Next.js behavior.

Based on the approach from #215 by @illegalcall (Dhruv Sharma). Thank you for the contribution! This PR takes the same direction with some fixes to match Next.js behavior more precisely.

What changed

Client-side: auto-skip SVGs (default behavior)

When src ends with .svg and dangerouslyAllowSVG is not enabled in config, the Image component automatically sets unoptimized = true. This means SVGs bypass /_vinext/image entirely and load directly from their source URL. This matches what Next.js does.

Server-side: SVG passthrough with security headers

When dangerouslyAllowSVG: true is set in next.config, SVGs that reach the image optimizer are passed through without transformation (no sharp processing). Security headers are applied:

  • Content-Security-Policy: script-src 'none'; frame-src 'none'; sandbox;
  • Content-Disposition set per config (default: inline)
  • X-Content-Type-Options: nosniff

New config options in next.config.images

  • dangerouslyAllowSVG (default: false) - allow SVGs through the optimizer
  • contentDispositionType (default: "inline") - controls Content-Disposition header
  • contentSecurityPolicy (default: "script-src 'none'; frame-src 'none'; sandbox;")

How Next.js does it

From Next.js source (shared/lib/get-img-props.ts):

if (isDefaultLoader && src.endsWith('.svg') && !config.dangerouslyAllowSVG) {
  unoptimized = true
}

The auto-skip is conditional on !dangerouslyAllowSVG, not unconditional. This is the key difference from #215, which always skipped SVGs regardless of config.

Files changed

  • config/next-config.ts - Added SVG-related config types
  • shims/image.tsx - Client-side conditional SVG auto-skip
  • server/image-optimization.ts - SVG passthrough logic, configurable security headers, ImageConfig type
  • index.ts - Inject dangerouslyAllowSVG config into client and build outputs
  • server/prod-server.ts - Thread image config through to optimization handler
  • deploy.ts - Pages Router worker entry passes image config
  • tests/shims.test.ts - Unit tests for all SVG handling paths

Tests

Added tests covering:

  • SVG auto-skip when dangerouslyAllowSVG is false (default)
  • SVG not auto-skipped when dangerouslyAllowSVG is true
  • isSafeImageContentType accepts/rejects SVG based on config
  • SVG passthrough without transformation in handleImageOptimization
  • SVG blocked (400) when dangerouslyAllowSVG is false
  • Custom CSP and Content-Disposition headers
  • Security headers applied on SVG passthrough

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/236 **Author:** [@southpolesteve](https://github.com/southpolesteve) **Created:** 3/3/2026 **Status:** ✅ Merged **Merged:** 3/3/2026 **Merged by:** [@southpolesteve](https://github.com/southpolesteve) **Base:** `main` ← **Head:** `fix/svg-image-support` --- ### 📝 Commits (1) - [`fcbf801`](https://github.com/cloudflare/vinext/commit/fcbf8015fbf04bc9355a3436bbe84d0d0484c030) fix: handle SVGs in image optimization to match Next.js behavior (#205) ### 📊 Changes **7 files changed** (+297 additions, -25 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/config/next-config.ts` (+6 -0) 📝 `packages/vinext/src/deploy.ts` (+14 -1) 📝 `packages/vinext/src/index.ts` (+43 -0) 📝 `packages/vinext/src/server/image-optimization.ts` (+43 -12) 📝 `packages/vinext/src/server/prod-server.ts` (+26 -9) 📝 `packages/vinext/src/shims/image.tsx` (+16 -3) 📝 `tests/shims.test.ts` (+149 -0) </details> ### 📄 Description ## Summary Fixes #205. SVGs passed to `/_vinext/image` now work correctly, matching Next.js behavior. **Based on the approach from #215 by @illegalcall (Dhruv Sharma).** Thank you for the contribution! This PR takes the same direction with some fixes to match Next.js behavior more precisely. ## What changed ### Client-side: auto-skip SVGs (default behavior) When `src` ends with `.svg` and `dangerouslyAllowSVG` is **not** enabled in config, the `Image` component automatically sets `unoptimized = true`. This means SVGs bypass `/_vinext/image` entirely and load directly from their source URL. This matches what Next.js does. ### Server-side: SVG passthrough with security headers When `dangerouslyAllowSVG: true` is set in `next.config`, SVGs that reach the image optimizer are passed through without transformation (no sharp processing). Security headers are applied: - `Content-Security-Policy: script-src 'none'; frame-src 'none'; sandbox;` - `Content-Disposition` set per config (default: `inline`) - `X-Content-Type-Options: nosniff` ### New config options in `next.config.images` - `dangerouslyAllowSVG` (default: `false`) - allow SVGs through the optimizer - `contentDispositionType` (default: `"inline"`) - controls Content-Disposition header - `contentSecurityPolicy` (default: `"script-src 'none'; frame-src 'none'; sandbox;"`) ## How Next.js does it From Next.js source (`shared/lib/get-img-props.ts`): ```ts if (isDefaultLoader && src.endsWith('.svg') && !config.dangerouslyAllowSVG) { unoptimized = true } ``` The auto-skip is conditional on `!dangerouslyAllowSVG`, not unconditional. This is the key difference from #215, which always skipped SVGs regardless of config. ## Files changed - `config/next-config.ts` - Added SVG-related config types - `shims/image.tsx` - Client-side conditional SVG auto-skip - `server/image-optimization.ts` - SVG passthrough logic, configurable security headers, `ImageConfig` type - `index.ts` - Inject `dangerouslyAllowSVG` config into client and build outputs - `server/prod-server.ts` - Thread image config through to optimization handler - `deploy.ts` - Pages Router worker entry passes image config - `tests/shims.test.ts` - Unit tests for all SVG handling paths ## Tests Added tests covering: - SVG auto-skip when `dangerouslyAllowSVG` is false (default) - SVG not auto-skipped when `dangerouslyAllowSVG` is true - `isSafeImageContentType` accepts/rejects SVG based on config - SVG passthrough without transformation in `handleImageOptimization` - SVG blocked (400) when `dangerouslyAllowSVG` is false - Custom CSP and Content-Disposition headers - Security headers applied on SVG passthrough --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 12:39:40 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#409
No description provided.