vercel/next.js · error · ImageError

"url" parameter is valid but upstream response is invalid

Error message

"url" parameter is valid but upstream response is invalid

What it means

Thrown by fetchExternalImage (ImageError 508) when the upstream returns a redirect (301/302/303/307/308) with a parseable Location header but the redirect counter has hit zero (default 3 hops exhausted). fetchExternalImage recurses with count-1 on each redirect; when count===0 it stops and reports the loop. The status 508 (Loop Detected) signals too many redirects.

Source

Thrown at packages/next/src/server/image-optimizer.ts:910

    if (err.name === 'TimeoutError') {
      Log.error('upstream image response timed out for', href)
      throw new ImageError(
        504,
        '"url" parameter is valid but upstream response timed out'
      )
    }
    throw err
  }

  const locationHeader = res.headers.get('Location')
  if (
    isRedirect(res.status) &&
    locationHeader &&
    URL.canParse(locationHeader, href)
  ) {
    if (count === 0) {
      Log.error('upstream image response had too many redirects', href)
      throw new ImageError(
        508,
        '"url" parameter is valid but upstream response is invalid'
      )
    }
    const redirect = new URL(locationHeader, href).href
    return fetchExternalImage(
      redirect,
      dangerouslyAllowLocalIP,
      maximumResponseBody,
      count - 1
    )
  }

  if (!res.ok) {
    Log.error('upstream image response failed for', href, res.status)
    throw new ImageError(
      res.status,
      '"url" parameter is valid but upstream response is invalid'

View on GitHub (pinned to 0ae8c72462)

Solutions

  1. Resolve the final image URL (follow redirects manually) and configure that direct URL in remotePatterns.
  2. Fix the upstream redirect configuration to reduce hop count or break the loop.
  3. Ensure the upstream returns the image directly (200) instead of redirecting.
  4. Cache the optimized result so the redirect chain is only walked once.

Example fix

# before: url redirects through many hops until count hits 0
/_next/image?url=https://host.example/redirect-to-cdn  # 508

# after: use the resolved final URL
/_next/image?url=https://cdn.example/final-photo.jpg&w=640&q=75
Defensive patterns

Strategy: retry

Validate before calling

// Resolve final image URL (following redirects) and store it to avoid long chains
async function resolveFinalImageUrl(u: string, maxHops = 5) {
  let cur = u, hops = 0
  while (hops++ < maxHops) {
    const r = await fetch(cur, { redirect: 'manual' })
    const loc = r.headers.get('location')
    if (loc && r.status >= 300 && r.status < 400) { cur = new URL(loc, cur).href; continue }
    return cur
  }
  throw new Error('too many redirects resolving image URL')
}

Prevention

When it happens

Trigger: Upstream responds with a chain of redirects longer than 3, or a redirect loop. Each hop decrements count; when it reaches 0 the throw at line 910 fires.

Common situations: The image URL is behind a redirect chain (auth gate -> CDN -> origin). A misconfigured upstream returns redirects back to itself (loop). HTTP<->HTTPS redirects that bounce. A URL shortener plus CDN producing multiple hops.

Related errors


AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06). Data as JSON: /api/errors/414b32026165ec7a. Report an issue: GitHub.