Skip to content

fix: handle SVGs in image optimization to match Next.js behavior#236

Merged
southpolesteve merged 1 commit intomainfrom
fix/svg-image-support
Mar 3, 2026
Merged

fix: handle SVGs in image optimization to match Next.js behavior#236
southpolesteve merged 1 commit intomainfrom
fix/svg-image-support

Conversation

@southpolesteve
Copy link
Copy Markdown
Collaborator

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

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Mar 3, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@236

commit: beed67c

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 3, 2026

Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original

@southpolesteve
Copy link
Copy Markdown
Collaborator Author

/bigbonk review please

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: SVG Image Optimization

This PR correctly implements SVG handling to match Next.js behavior across the client-side image component, server-side optimization, and production server paths. The approach is sound.

What works well

  • Client-side auto-skip logic (image.tsx): When src ends with .svg and dangerouslyAllowSVG is not enabled, unoptimized is automatically set to true so SVGs bypass /_vinext/image entirely. This matches Next.js's get-img-props.ts behavior.
  • Server-side passthrough (image-optimization.ts): SVGs that reach the optimizer skip sharp processing (correct — SVG is vector, resize is meaningless) and get security headers applied.
  • Security headers: CSP (script-src 'none'), X-Content-Type-Options: nosniff, and Content-Disposition defaults all match Next.js.
  • Config propagation: dangerouslyAllowSVG is injected into client builds via process.env.__VINEXT_IMAGE_DANGEROUSLY_ALLOW_SVG define, and into server builds via vinextConfig.images serialization and image-config.json.
  • Test coverage: 149 lines of new tests covering isSafeImageContentType with/without SVG flag, handleImageOptimization SVG passthrough/blocking, security headers, custom CSP/Content-Disposition, and client-side auto-skip logic. Solid coverage.

Coverage across all server paths

Path SVG handling Status
image-optimization.ts (Workers) SVG passthrough + security headers
prod-server.ts App Router Reads image-config.json, applies SVG blocking
prod-server.ts Pages Router Reads from vinextConfig.images, applies SVG blocking
deploy.ts Pages Router worker Extracts imageConfig from vinextConfig.images
index.ts vinext:image-config Writes image-config.json for App Router builds
Dev servers (index.ts, app-dev-server.ts) Redirect-only, no SVG blocking ✅ (acceptable — dev doesn't optimize images)

Minor observations (non-blocking)

  1. Dev/prod behavior difference: In dev, SVGs always work because the image endpoint just redirects to the original asset URL. In prod, SVGs are blocked unless dangerouslyAllowSVG: true. This is actually reasonable since dev mode doesn't transform images at all, but it's a subtle difference developers might notice when deploying.

  2. App Router worker entry in deploy.ts: The Pages Router path explicitly extracts imageConfig from vinextConfig.images, but the App Router path relies on image-config.json being read at runtime in prod-server.ts. This works but the two paths use different config propagation mechanisms — worth keeping in mind for future maintenance.

LGTM — nice work matching the Next.js behavior faithfully.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Mar 3, 2026

Review posted on PR #236. Here's a summary of what I found:

Verdict: Approved

The PR correctly implements SVG handling to match Next.js behavior across all critical paths:

  • Client-side: Auto-sets unoptimized = true for .svg sources when dangerouslyAllowSVG isn't enabled (matching Next.js's get-img-props.ts)
  • Server-side: SVGs that reach the optimizer skip sharp processing and get proper security headers (CSP, nosniff, Content-Disposition)
  • Config propagation: Properly flows through both client builds (via define) and server builds (via vinextConfig.images and image-config.json)
  • All production server paths covered: Workers entry, App Router prod, Pages Router prod all handle SVG blocking correctly
  • Test coverage: 149 lines of new tests with good breadth

Two minor non-blocking observations noted: dev/prod behavior difference (acceptable since dev doesn't optimize images) and the two different config propagation mechanisms between App Router and Pages Router worker entries.

github run

@illegalcall
Copy link
Copy Markdown
Contributor

Thanks @southpolesteve.

Co-authored-by: Dhruv Sharma dhruvsharma@users.noreply.github.com

This isnt me, is it possible to use dhruvcoding67@gmail.com? 🥹

SVGs are auto-skipped on the client side (set unoptimized=true) unless
dangerouslyAllowSVG is enabled in next.config. When dangerouslyAllowSVG
is true, SVGs that reach the server are passed through without
transformation, with security headers (CSP, Content-Disposition)
applied.

Config options added to NextConfig.images:
- dangerouslyAllowSVG (default: false)
- contentDispositionType (default: "inline")
- contentSecurityPolicy (default: "script-src 'none'; frame-src 'none'; sandbox;")

Co-authored-by: Dhruv Sharma <dhruvcoding67@gmail.com>
@southpolesteve southpolesteve force-pushed the fix/svg-image-support branch from d1f9bf9 to fcbf801 Compare March 3, 2026 14:40
@southpolesteve southpolesteve merged commit 5ece95e into main Mar 3, 2026
31 of 32 checks passed
@southpolesteve southpolesteve deleted the fix/svg-image-support branch March 3, 2026 14:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SVG's do not render (400)

2 participants