The page did not crash at random; the iPhone was protecting itself from the page.
That distinction changes what you go looking for. A crash is a defect in your code. A reload is a decision WebKit made on your behalf, under pressure, after the tab asked for more memory than the system was willing to keep handing it. The banner says a problem occurred with this webpage. The honest translation is that the tab got too expensive and the operating system took the money back.
You have probably shipped this bug. Desktop is fine, Lighthouse is green, then someone opens the site on an iPhone, scrolls past the section with four autoplaying clips, and the page blinks white and starts over from the top.
How many video elements does your heaviest section mount at once? Most people cannot answer that, which is the actual problem.
Why does mobile Safari reload a page with several videos?

Mobile Safari reloads the page when WebKit decides the tab's memory footprint exceeds what the device can hold, and a discard is the cheapest recovery.
There is no single published number you can code against. The ceiling moves with the device, the iOS version, how much pressure other apps are putting on the system, and what your page is holding in graphics memory at that moment. Anyone quoting you a hard megabyte limit is repeating one measurement from one device on one afternoon.
What you can reason about is the shape of the pressure. WebKit's own memory debugging guide for Web Inspector splits a page into four lanes: JavaScript, Images ("decoded image data"), Layers ("WebKit's tile grid, page content using compositing layers"), and Page for everything else. A media-heavy landing page pushes on three of those at once. Your JavaScript heap is usually the innocent one, which is why heap profiling alone finds nothing and everyone concludes the framework is haunted.
A smooth desktop scroll animation can still be a mobile memory leak

A smooth desktop scroll animation can still be a mobile memory leak with good frame timing. Sixty frames per second tells you the compositor kept up. It tells you nothing about how much memory the compositor had to allocate to get there.
Pinned sections are the usual culprit. You take a tall scroll container, pin a stack of project cards inside it, and animate between them as the user scrolls. On a desktop GPU with gigabytes of headroom that is a pleasant effect. On a phone it means every card in the stack is laid out at once, composited into its own layer, and holding a decoded frame buffer if the card carries a video.
The frame timing stays good right up to the moment the page disappears. That is the trap. Your performance instinct is trained on jank, and this failure mode has none.
What actually stacks up in memory?
Decoded video frames, poster images and compositing layers stack up, and a small file on the wire becomes a large allocation once decoded.
The clearest public numbers here are old and still directionally right. In an August 2014 WHATWG mailing-list thread, Duan Yao measured per-element video memory and reported that "for each playing or paused or preload=auto video element, the memory usage is up to 3080MB; for those with preload=metadata, memory usage is 613MB; for those with preload=none, memory usage is not notable."
Six clips at preload="auto" is, by that measurement, most of a phone's realistic budget before your CSS has done anything. Actually, that undersells it, because posters and compositing layers are separate line items on top.
| What the element holds | Rough per-element cost | When it is the right call |
|---|---|---|
preload="none" | Not notable | Any clip that is not currently on screen |
preload="metadata" | 6–13 MB | A clip whose duration or dimensions you need before playback |
preload="auto", playing or paused | 30–80 MB | The one clip the user is actually looking at |
Read the third column first. The decision is not which value is best, but how many elements are allowed to sit in the expensive row at the same time.
The same message contains the sentence most people get wrong: "Even if a video element is or becomes invisible, either by being out of viewport, having display:none style, or being removed from the active DOM tree (but not released), almost same amount of memory is still occupied."
Hiding is not unloading.
How do you measure this instead of guessing?
Measure by removing one media system at a time on a physical iPhone until the reload stops, then adding each one back until it returns.
Tooling first. Connect the phone by cable, enable Web Inspector on the device, and attach Safari on the Mac. In Timelines, the Memory lane gives you that four-category breakdown and the JavaScript Allocations lane gives you heap snapshots. WebKit's guide also describes how to separate a real leak from normal churn: repeat a memory-neutral action and compare snapshots. "If you perform the action five times and memory only increased the first time, then there likely isn't a problem, but if you saw a steady increase each time, then you've likely uncovered a leak."
Then the plan itself:
- Reproduce on a real device, three times, on the same route. A reload you cannot trigger twice is not yet a bug you can fix.
- Record a Memory timeline across the failing scroll and note which lane climbs. Layers climbing points at compositing. Images climbing points at decode. Page climbing usually points at retained DOM.
- Turn off autoplay on every video and scroll again. If the reload stops, the problem is concurrent decode rather than your animation code.
- Restore autoplay and remove the pinned scroll section instead. If the reload stops now, the problem is layer count.
- Restore both and halve your poster dimensions. If that alone buys stability, you were paying to decode posters far larger than the box they render into.
- Add one system back at a time until the reload returns. The last thing you added is the thing to redesign, not the thing to micro-optimize.
The first evening I chased this, I had the phone cabled to the Mac, the same section on loop, and a growing certainty that the animation library was at fault. It was not.
Desktop responsive mode will not reproduce any of this. It has your laptop's memory.
Is this a WebKit bug or my bug?
Usually yours, because discarding a page under memory pressure is documented behavior rather than a defect you can file. The exceptions have known signatures. Developers reported that from Safari 15 on iOS, pointing a video src at a blob URL caused the blob to be re-fetched endlessly until Safari reloaded the page, with no equivalent on macOS. Search your exact symptom before you rewrite an architecture.
The pattern that actually held
Here is how that went on this portfolio.
On my portfolio, the iPhone repeatedly became unstable around the media-heavy project and ecosystem sections even though desktop performance looked fine. The fix was architectural: mobile stopped using the giant pinned project stack, and only the visible project card kept a video source attached. Pausing hidden videos was not enough; unloading their sources was what stopped decoded media from accumulating.
Unloading means clearing the source and telling the element to forget it, not merely pausing:
function unload(video) {
video.pause();
video.removeAttribute("src");
while (video.firstChild) video.removeChild(video.firstChild);
video.load(); // forces the element to drop the previous resource
}
The load() call is the part people skip. Without it the element can hold onto the previous resource, and on some iOS versions it will re-fetch the URL you thought you had just removed.
Four rules came out of that work, and they are the ones I apply by default now:
- One source at a time. Only the active card gets a
src. Every other card is an empty<video>with a poster, waiting its turn. - Mobile gets document flow, not a pinned stack. Scroll choreography is a desktop feature. On a phone those sections render as ordinary stacked blocks, which is cheaper to composite and easier to read with one thumb. The Elpida Solutions real-estate website goes a step further and ships separate landscape and portrait media, so a phone never downloads or decodes the wide cut at all.
- Posters stay small. A poster is decoded image data in the Images lane whether or not the clip ever plays. Size it for its container.
- Respect
prefers-reduced-motion. Users who set it get the static composition, which happens to be the low-memory path as well.
None of this is exotic. It is the same discipline as keeping a Flutter list smooth when it is full of images and animations: decide what is allowed to be expensive right now, and make everything else cheap by construction.
Fixes that are not fixes
Four things look like solutions and are not.
Setting display: none. The element keeps its memory, as the measurement above spells out. If your fix was a CSS class, you did not fix it.
Preloading every clip so playback feels instant. This is the single most common cause of the reload you are trying to stop. Instant playback on a page that reloads is a worse experience than two hundred milliseconds of delay on a page that survives.
Validating in desktop responsive mode. It catches layout bugs and tells you nothing about memory.
Blaming the framework. I have watched a team spend a week bisecting a rendering library over a reload that turned out to be six autoplaying clips. If the site is a Next.js build, the framework is not deciding how many video elements you mount. You are.
What Safari 26 changed
Safari 26.0, released on 15 September 2025, shipped scroll-driven animations, which tie a CSS animation to scroll position or to an element's progress through the viewport via animation-timeline and animation-range.
That matters here for a reason that is easy to miss. Scroll-driven animations do not shrink your videos. What they remove is the scaffolding built around scroll effects: the tall pinned container, the JavaScript scroll listener, the extra wrapper elements that each earn a compositing layer. Less scaffolding means less in the Layers lane, which is often the lane that was actually climbing.
Do not read that as permission. If the cheaper animation tempts you into a longer, denser scroll story, you land back where you started with better frame timing.
One more thing that is not a bug. WebKit's inline video policy for iOS states that autoplay elements "will only begin playing when visible on-screen" and "will pause if they become non-visible." That pausing is correct behavior, and it is also not the same as releasing memory. Plenty of teams read the policy, assume the platform is cleaning up for them, and stop there.
The iPhone checklist before you ship
Run this on a physical device. Not the simulator.
- Portrait, full page top to bottom, twice.
- Landscape, same route, same two passes.
- Scroll back upward through every media section. Upward passes re-enter sections in a different order and catch cleanup that only ever runs one way.
- Switch to another Safari tab, wait thirty seconds, switch back. A page discarded here was already sitting near the ceiling.
- Lock the phone, wake it, and return to the tab.
- Repeat your worst section five times while watching the Memory timeline. Roughly flat is fine. A staircase is not.
- Re-run the whole list with Low Power Mode on.
If step 6 draws a staircase, stop optimizing and go back to the architecture question: how many media elements are alive at once, and who is responsible for killing them?
That is the question every real fix answers. The static, tracker-free chess club website for Šahovski klub Dubica never had this problem, because it never had more than one heavy thing on screen at a time. Constraint by default beats cleanup by exception.
Open your own site on an iPhone now, scroll to the heaviest section, and count the video elements currently holding a source. If that number is larger than one, you already know what you are doing this afternoon.
