RORK LABJP
FREETRY — Rork announced on X that Rork Max is free to try for a limited time, a chance to touch the $200/month tier without paying up frontONESHOT — Rork Max claims to one-shot almost any app for iPhone, Watch, iPad, TV, and Vision Pro, including builds that lean on AR and 3DXCODE — Positioned as a website that replaces Xcode: one click to install on device, two clicks to publish to the App Store, powered by Swift, Claude Code, and OpusPAPERLINE — Rork acquired the app builder Paperline and says it will stay acquisitive to bring in engineering talentSKILLS — Rork's app-store-connect-cli-skills repo was updated on July 9, a sign the submission tooling keeps getting attentionMARKET — Gartner expects 75% of new applications to be built with low-code or no-code tools by the end of 2026, up from under 25% in 2020FREETRY — Rork announced on X that Rork Max is free to try for a limited time, a chance to touch the $200/month tier without paying up frontONESHOT — Rork Max claims to one-shot almost any app for iPhone, Watch, iPad, TV, and Vision Pro, including builds that lean on AR and 3DXCODE — Positioned as a website that replaces Xcode: one click to install on device, two clicks to publish to the App Store, powered by Swift, Claude Code, and OpusPAPERLINE — Rork acquired the app builder Paperline and says it will stay acquisitive to bring in engineering talentSKILLS — Rork's app-store-connect-cli-skills repo was updated on July 9, a sign the submission tooling keeps getting attentionMARKET — Gartner expects 75% of new applications to be built with low-code or no-code tools by the end of 2026, up from under 25% in 2020
Articles/AI Models
AI Models/2026-07-17Advanced

The Three-Minute Video That Kept Failing — Moving Rork's Gemini Uploads Off the Worker

How I rerouted video uploads to the Gemini Files API from a Cloudflare Workers relay to a direct device-to-Google path — why the 128MB isolate limit breaks the relay, how to hand out a resumable upload URL without leaking your API key, and how resolution and frame rate actually decide the bill.

Rork515Gemini7Files APIVideo AnalysisCloudflare Workers24Expo149Indie Dev36

Premium Article

A 28-second clip shot on my iPhone went through every time. The analysis came back looking exactly like I wanted.

Then I asked a friend to record three minutes of footage, and the upload simply stopped coming back. No useful error. Just a long pause and eventually a 500.

I spent the first hour suspecting the model. Wrong format, maybe, or a video too long for the request. The logs said otherwise: the thing was dying well before it ever reached Gemini. What had broken was not the inference. It was the pipe.

Twenty-eight seconds passes, three minutes stalls

The setup was reasonable enough, as these things go.

An Expo app picks a video, posts it as multipart to a Cloudflare Worker, and the Worker relays it to the Gemini Files API. I did not want the API key sitting inside the app bundle, so I put a server in the middle. As an indie developer who would rather not run a backend at all, I found Workers a tempting place to land.

The Worker looked like this.

// ❌ The original — the whole video lands in the Worker's heap
async function handleVideoUpload(request: Request, env: Env): Promise<Response> {
  const formData = await request.formData();
  const videoFile = formData.get('video') as File;
 
  // Every byte of the video is materialized here
  const arrayBuffer = await videoFile.arrayBuffer();
 
  const initResponse = await fetch(`${UPLOAD_URL}?key=${env.GEMINI_API_KEY}`, {
    method: 'POST',
    headers: {
      'X-Goog-Upload-Protocol': 'resumable',
      'X-Goog-Upload-Command': 'start',
      'X-Goog-Upload-Header-Content-Length': arrayBuffer.byteLength.toString(),
      'X-Goog-Upload-Header-Content-Type': videoFile.type,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ file: { display_name: videoFile.name } }),
  });
 
  const uploadUri = initResponse.headers.get('X-Goog-Upload-URL');
 
  // ...and then every byte gets shipped a second time
  return fetch(uploadUri!, {
    method: 'POST',
    headers: {
      'Content-Length': arrayBuffer.byteLength.toString(),
      'X-Goog-Upload-Offset': '0',
      'X-Goog-Upload-Command': 'upload, finalize',
    },
    body: arrayBuffer,
  });
}

One line did all the damage: await videoFile.arrayBuffer().

The video was passing straight through the Worker

A Cloudflare Worker isolate is capped at 128MB of memory, covering the JavaScript heap and any WebAssembly allocations. Paying for the Workers plan does not raise it. And the cap applies per isolate, not per invocation — which matters more than it first sounds.

Meanwhile the request body limit on the Free plan is 100MB. So the platform will happily accept a video it has no room to hold.

Here is what my phone actually produces.

Capture settingLengthMeasured sizeResult after arrayBuffer
1080p / 30fps28s~52MBPasses
1080p / 30fps1m 10s~130MB413
720p / 30fps3m~96MBIntermittent 500
4K / 60fps30s~190MB413

The three-minute 720p clip was the nastiest of the four. At 96MB it slips under the body limit and gets accepted. But once those 96MB are on the heap, a single video owns most of the 128MB budget. Add one concurrent request sharing that isolate and you are over.

That is why it failed intermittently. Testing alone, it worked. With a few people touching it at once, it fell over. I could not reproduce it on demand because the bug needed company.

And even on the runs that succeeded, the shape was wasteful. The bytes climb from the phone to Cloudflare, then climb again from Cloudflare to Google. The same payload crosses the network twice while the Worker burns CPU time doing nothing but holding it. The only thing that relay bought me was hiding the API key.

Hiding a key does not require moving the video.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Why loading a video into a Worker's arrayBuffer collides with the 128MB isolate ceiling, and the upload path that removes the problem instead of tuning around it
Working Worker and Expo code that keeps the API key server-side while the video travels straight from the device to Google
Using low media resolution and a lower fps to cut roughly 300 tokens per second of video down to roughly 100, and when that trade costs you the answer
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

AI Models2026-07-05
When Your Finance App's AI Keeps Dumping Everything Into 'Other' — Field Notes on Catching Silent Classification Drift
An AI expense classifier in a Rork finance app can be accurate at launch and quietly decay over months until the monthly advice goes wrong. Here is how I instrumented confidence and category distribution to get ahead of the drift, with working code.
AI Models2026-06-19
Before You Pay $200/mo for Rork Max, Map How Far Expo Reaches in Three Tiers
Wanting widgets or Live Activities makes Rork Max tempting, but most of those features are reachable from the Expo setup that standard Rork generates. Here is how I sort each Apple-native feature into three tiers—reachable in Expo, reachable with a custom module, or where Max is the pragmatic answer—and verify which tier my app is in before paying.
AI Models2026-06-14
Calling Apple Foundation Models from a Rork (Expo) App: Bridging On-Device AI Through a Native Module
Rork generates Expo (React Native) apps, but Apple Foundation Models ships as a Swift framework you can't touch from JavaScript. Here's how to write an Expo Modules API bridge, gate it by availability, and fall back to the cloud on unsupported devices.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →