remotion-dev/remotion · error · Error
Server returned status code ${res.status} for ${resolvedUrl}
Error message
Server returned status code ${res.status} for ${resolvedUrl} and range ${requestedRange} What it means
In makeFetchRequest, after the fetch resolves the parser checks res.ok; any non-2xx status throws with the status code, URL, and requested range. This surfaces HTTP-level failures (404, 403, 500, etc.) before the parser tries to interpret bytes.
Source
Thrown at packages/media-parser/src/readers/from-fetch.ts:156
Range: `bytes=${requestedRange}-`,
}
: {
Range: `bytes=${`${requestedRange[0]}-${requestedRange[1]}`}`,
};
const res = await fetch(resolvedUrl, {
headers,
signal: ownController.signal,
cache,
});
const contentRange = res.headers.get('content-range');
const parsedContentRange = contentRange
? parseContentRange(contentRange)
: null;
if (!res.ok) {
throw new Error(
`Server returned status code ${res.status} for ${resolvedUrl} and range ${requestedRange}`,
);
}
const {supportsContentRange} = validateContentRangeAndDetectIfSupported({
requestedRange,
parsedContentRange,
statusCode: res.status,
});
if (controller) {
controller._internals.signal.addEventListener(
'abort',
() => {
ownController.abort(new MediaParserAbortError('Aborted by user'));
},
{once: true},
);View on GitHub (pinned to 78fe4bb3fd)
Solutions
- Open the URL in a browser / curl `-I` to see the exact status and fix the root cause (404 -> correct path, 403 -> refresh credentials).
- For presigned URLs, regenerate with a longer expiry before parsing.
- Ensure any required auth/cookie headers are present and that CORS allows your origin.
- Retry with backoff for transient 5xx/429, and surface a clear error to the end user.
Example fix
// before
await parseMedia({src: presignedUrl, fields: {durationInSeconds: true}});
// after: refresh presigned URL and handle status
const url = await getFreshPresignedUrl(key); // TTL-aware
try {
await parseMedia({src: url, fields: {durationInSeconds: true}});
} catch (e) {
if (/status code 40[13]/.test(e.message)) throw new Error('Media URL expired or forbidden');
throw e;
} Defensive patterns
Strategy: retry
Validate before calling
// HEAD-check the URL before parsing
const head = await fetch(url, {method: 'HEAD'});
if (!head.ok) throw new Error(`Media URL returned ${head.status}`); Try / catch
async function withRetry<T>(fn: () => Promise<T>, retries = 3): Promise<T> {
for (let i = 0; i < retries; i++) {
try { return await fn(); }
catch (e) {
const m = e instanceof Error ? e.message : '';
if (/status code 5\d\d|429/.test(m) && i < retries - 1) {
await new Promise(r => setTimeout(r, 2 ** i * 500)); continue;
}
throw e;
}
}
throw new Error('unreachable');
}
await withRetry(() => parseMedia({src: url, fields: {durationInSeconds: true}})); Prevention
- Refresh presigned URLs close to parse time; do not cache them long.
- Verify URLs with curl -I before parsing.
- Ensure CORS and auth headers are set for your origin.
- Retry transient 5xx/429 with backoff; surface 4xx as user errors.
When it happens
Trigger: The media URL returns a non-2xx HTTP status: 404 (wrong URL / asset removed), 403 (auth/permissions, expired presigned URL), 401 (auth required), 5xx (server error), or 429 (rate limited).
Common situations: Expired S3 presigned URLs, wrong/deleted asset paths, missing auth headers, CORS-preFlight failures surfacing as opaque errors, CDN origin errors, rate limiting, or hotlink protection blocking the request.
Related errors
- Cannot read media ${src} without a content length. This is c
- Cannot read media without it supporting the "Content-Range"
- Range header (${requestedRange}) does not match content-rang
- Failed to fetch ${src} (HTTP code: ${res.status})
- No body
AI-assisted analysis of remotion-dev/remotion@78fe4bb3fd (2026-08-12).
Data as JSON: /api/errors/a5aee8994892d045.
Report an issue: GitHub.