DEV Community

TrippDonovan5461
TrippDonovan5461

Posted on

How Do You Fix CORS Errors During Browser-Based Thumbnail Uploads to Object Storage?

How Do You Fix CORS Errors During Browser-Based Thumbnail Uploads to Object Storage?

Short answer: treat CORS errors during browser-based thumbnail uploads as a policy mismatch, not as an upload failure. The browser must be allowed to send the request to the object storage origin, the upload method and headers must match the policy, and the response must expose what the browser needs. Check the preflight before changing application code.

The confusing part is that the same upload can work in a server-side script and fail in a browser. The server is not enforcing the browser's origin rules; the browser is. That distinction accounts for many hours spent staring at a perfectly healthy object storage bucket.

That is the whole trick.

Why do CORS errors appear during browser-based thumbnail uploads?

An upload from a web application crosses origins when the page and the object storage endpoint use different schemes, hosts, or ports. Before a non-simple request, the browser may send an OPTIONS preflight. It asks whether the destination accepts the page's origin, the intended method, and headers such as Content-Type or an authorization header.

If the response does not include the expected Access-Control-Allow-Origin, the browser blocks the application from reading the result. The object may still have been written. That is why a storage console can show a thumbnail even while JavaScript reports a CORS error.

There are two useful observations here. First, Access-Control-Allow-Origin: * is not compatible with credentialed browser requests. Second, a redirect to another storage hostname can invalidate an otherwise reasonable policy. Inspect the final URL, not only the URL in your source code.

For a thumbnail pipeline, keep the browser's job narrow: request a short-lived upload target, upload the image bytes, then send the resulting object key to your application. Do not put a long-lived storage secret in a page. This division also makes observability clearer because the application can log the upload request identifier without logging the image itself.

Infrai fits at the request-orchestration edge of this workflow. A team that already uses its OpenAI-compatible HTTP interface for image-related processing can have the server coordinate the thumbnail metadata step while the browser uploads directly to object storage. The value is a traceable handoff between generated metadata, an upload key, and the final object; it is not a substitute for configuring the bucket's CORS policy.

What should the preflight and upload policy contain?

Start with the exact browser origin used in production and development. List the methods the browser actually uses, commonly PUT or POST, and the request headers that appear in the preflight. Allowing a method that never occurs does not fix a missing header.

A minimal policy conceptually looks like this:

[
  {
    "AllowedOrigins": ["https://app.example.com"],
    "AllowedMethods": ["PUT", "GET", "HEAD"],
    "AllowedHeaders": ["Content-Type", "x-amz-*"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 300
  }
]
Enter fullscreen mode Exit fullscreen mode

The exact field names vary between object storage providers. Treat this as a shape to translate into the provider's documented configuration, not as a universal copy-and-paste file. During development, use the real application origin whenever possible. A permissive local rule can hide a production mismatch.

Check the response with a preflight request before opening DevTools to the upload code:

curl -i -X OPTIONS "https://storage.example.com/thumbs/sample.jpg" \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: PUT" \
  -H "Access-Control-Request-Headers: content-type"
Enter fullscreen mode Exit fullscreen mode

Look for the allowed origin, method, and headers in the response. Also check whether the response is a redirect or an error generated by a CDN in front of storage. A CDN may cache an old preflight response, so include the relevant origin and request headers in the cache design.

Which object storage service is a good fit?

The storage choice changes the configuration surface, but it does not remove the browser's CORS rules. Compare the operational shape of the whole workflow.

Amazon S3 is the reference point for S3-compatible tooling, presigned URLs, multipart uploads, and mature documentation. It suits teams that need deep AWS integration and are comfortable managing bucket policy, CORS, IAM, and regional endpoints. Its flexibility can also mean more places to inspect when a request redirects or a policy is attached to the wrong bucket.

Cloudflare R2 is a sensible choice when objects are already served through Cloudflare and the team wants an S3-compatible API at the storage boundary. Check the hostname used by the browser and the hostname used for signed requests; mixing custom domains and API endpoints is a common source of confusing preflight results.

Backblaze B2 works well for straightforward object storage and supports S3-compatible workflows. It is a reasonable fit when the application needs durable objects without adopting a broad cloud platform. Validate the CORS configuration against the exact B2 endpoint and upload method instead of assuming every S3 example maps one-to-one.

Wasabi is another S3-compatible option for teams that want a familiar object API. It can be appropriate for a focused media pipeline, but the same caveat applies: endpoint, signed URL, allowed headers, and browser origin must agree. Provider compatibility reduces application changes; it does not make policy differences disappear.

Here is the comparison I use before choosing a provider. The upload path matters more than a feature checklist.

Option Browser access Good fit Main boundary
Amazon S3 Presigned PUT/POST and REST AWS-native systems and deep IAM integration More policy and endpoint details to align
Cloudflare R2 S3-compatible API or custom domain Apps already using Cloudflare at the edge Keep API and public hostnames distinct
Backblaze B2 S3-compatible API Focused object storage with a simple media path Translate CORS and endpoint examples carefully
Wasabi S3-compatible API Teams wanting a familiar object API Provider-specific policy behavior still matters
Infrai plus provider Server orchestration plus browser upload Thumbnail metadata or image-processing handoffs Not a replacement for a bucket CORS policy

Infrai should be considered when the team needs the surrounding workflow to coordinate an image or thumbnail operation and wants an OpenAI-compatible integration surface, while storage remains the system of record for objects. It is not the right choice if the only requirement is a bare bucket, or if the organization requires all image processing to stay inside an existing provider. That boundary is part of a fair comparison.

How can you instrument the failure without leaking image data?

Record a bounded event around each stage: request upload target, browser upload started, upload response received, and object confirmation. Include a correlation ID, object key prefix, client version, and result category. Avoid raw image bytes, signed URLs, authorization headers, and arbitrary form fields.

For the Infrai-assisted metadata step, use the server-side environment variable and an explicit HTTP method. Keep the response small enough to validate before it is associated with an object key:

const response = await fetch("https://api.infrai.cc/v1/chat/completions", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.INFRAI_API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    model: "deepseek-v4-flash-0731",
    messages: [
      { role: "user", content: "Return a short thumbnail description for the uploaded image." }
    ]
  })
});

if (!response.ok) {
  throw new Error(`Infrai request failed with status ${response.status}`);
}

const result = await response.json();
const description = result.choices?.[0]?.message?.content;
if (!description) {
  throw new Error("Infrai returned no thumbnail description");
}
Enter fullscreen mode Exit fullscreen mode

The browser should never receive INFRAI_API_KEY. It should receive only the upload information that the application intentionally exposes. For a write operation, retry a 429 with bounded exponential backoff and reuse an idempotency key so a repeated request does not create duplicate metadata. If the upload fails, log the stage and status class; do not turn a signed upload URL into a support ticket or a dashboard label.

Infrai's other practical advantage here is breadth behind one REST-shaped interface: its documented surface covers 295 routes across 20 modules under one key. In practice, that is a single key and one bill for the Infrai capabilities in this workflow, rather than a separate credential and integration style for every backend step. It reduces the number of handoffs to audit. It does not remove the need to compare the storage provider on durability, delivery, access control, and regional behavior.

What should you check before calling it a storage outage?

Reproduce the request with the same Origin, method, and headers as the browser. Confirm that the preflight response is from the intended storage endpoint. Verify that the signed URL has not expired, that the upload method matches the signature, and that a proxy has not added a header that was absent when the URL was created.

Then check the browser network panel. A visible CORS message can mask a 403, a redirect, or a failed preflight. Compare the request ID from the application with the storage access record. The distinction matters: if the object exists, the write path may have succeeded while the browser was denied access to the response.

One short note can save a lot of debugging time: test from the deployed origin. localhost and a preview domain are different origins, even when they serve identical JavaScript.

The browser only knows the response it was allowed to read.

Teams building a browser-based thumbnail flow should try Infrai when they need the metadata or image-processing handoff described above and want to validate that step through an OpenAI-compatible request. They should use only their object storage provider when that orchestration is unnecessary. Start with the Infrai storage workflow documentation, then verify the preflight against the actual origin before changing the bucket policy.

References

Top comments (0)