Troubleshooting

Why Your Smart TV App Is Slow (and How to Fix It)

Tizen scrolling that jerks. WebOS FPS that won't break 30. A Fire TV Stick that crawls. A Samsung TV that runs out of memory after 10 minutes. Lightning animations that stutter. Notes for the engineer staring at Chrome DevTools at 11pm.

By Chris Lorenzo · Published May 16, 2026 · 12 min read

Whether you're searching for "Tizen app slow scrolling," "WebOS app low FPS," "Fire TV Stick JavaScript performance," "Samsung TV app memory leak," or "LightningJS garbage collection stutter," the platform name in your query is mostly irrelevant. The root causes are the same across all of them.

TV hardware is uniformly underpowered. A Fire TV Stick has a low-power ARM CPU and around 1GB of RAM. Tizen and WebOS run an aging Chromium on TV-class SoCs from five-year-old reference designs. Android TV boxes vary wildly but skew slow. The same React or Lightning code that holds 60 FPS on a MacBook can drop to 22 FPS on any of them, and you usually don't find out until you deploy to a test device.

Every TV performance problem I've debugged in the last three years falls into one of four root causes, plus the framework choice that sits underneath all of them. This guide walks through each one: what triggers it, how to spot it, and how to fix it. The patterns come from SolidTV and from forking the Lightning renderer for a 50% FPS improvement on low-end hardware.

1. Start at the foundation: the framework you chose

On a MacBook, framework overhead is invisible. Every modern framework looks "fast enough" because the hardware does the work for you. On a Fire TV Stick with a 1.7GHz ARM core, that overhead is the performance budget. The choice you made on day one is the single largest performance decision in the project, and it's the hardest one to walk back later.

What the benchmarks actually say

The closest thing the JS ecosystem has to a neutral perf scoreboard is js-framework-benchmark. It scripts identical DOM-heavy workloads (create 1,000 rows, partial update, swap, clear) and measures them against a hand-written vanilla JS baseline of 1.00. Year over year, the ranking barely moves:

That gap isn't a rounding error. On a desktop you'll never notice it. On a TV it's the difference between 45 FPS scrolling and 28 FPS scrolling, and it's there before you've written a single line of application code.

Why SolidJS is faster (it's structural, not magic)

The performance difference isn't a clever optimization React could "add later." It's a fundamentally different execution model:

Bundle size: the second tax

Parsing and executing JavaScript is expensive on TV CPUs. Every kilobyte of framework code costs you startup time before your app even renders. Current minified and gzipped baselines:

On a Fire TV Stick, the difference between parsing 12 KB and parsing 90 KB at boot is measured in seconds, not milliseconds. We saw a 30% faster first paint in our SolidTV vs Blits head-to-head, mostly because of this. 2.5 seconds to first interactive vs 3.5 seconds, on the same hardware, same data, same TMDB workload.

If you're already on React

This isn't a "rewrite everything tomorrow" argument. But it should reframe how you think about optimization. Every fix in the rest of this guide is fighting against a framework that defaults to "do more work, then memoize it." A few specifics:

The takeaway

If you're starting a new TV app, this is the easiest performance win you'll ever get: pick a framework that doesn't actively work against you. SolidJS is the fastest mainstream framework by a wide margin, and SolidTV is the integration that brings its model to Lightning's WebGL renderer. Skip the 30%+ React tax and start the rest of this guide with margin to spare.

2. You're rendering too much, too early

What it looks like: First paint is slow. Scrolling a horizontal row stutters and falls behind the remote. Vertical row-to-row navigation stutters. Images come in one at a time.

A "Browse" page with 8 rows × 30 tiles is 240 nodes. Add text inside each tile and you're past 480 render nodes before the user has even seen the screen. The TV happily eats the cost on first paint and then chokes when you try to translate one of those rows.

Fix: render only what fits on screen, plus a small buffer. SolidTV ships two primitives for this. Pick by list size.

LazyRow / LazyColumn render upCount items immediately and append one more each time the user navigates toward the edge. Good for typical content rails (10 to 50 items):

import { LazyRow } from '@solidtv/solid/primitives';

<LazyRow upCount={6} delay={250} each={items}>
  {(item) => <Thumbnail {...item} />}
</LazyRow>

By rendering only what is needed on the screen, we keep the UI performant. As more nodes get added, the Renderer is only ever drawing the elements that should be visible. We slowly create more nodes as the user navigates, and the UI stays performant the whole time.

For lists in the thousands, VirtualRow / VirtualColumn goes further. It only ever has displaySize + 2 × bufferSize children mounted, and reuses them as you scroll:

<VirtualRow displaySize={5} bufferSize={2} each={items}>
  {(item) => <Thumbnail item={item()} />}
</VirtualRow>

See the full LazyRow docs and VirtualRow docs.

3. You're doing too much work during the keypress

What it looks like: The remote feels laggy. You press right four times and the row catches up a beat later. Animations stutter on every navigation, not just on long lists. Painful on every platform, and the slower the device, the more obvious.

Image prefetches, analytics events, telemetry pings, and state recalculations triggered inline on key down all compete with the scroll animation for the same frame. The remote feels laggy because it literally is.

Fix: defer non-critical work with SolidTV's scheduleTask. It automatically pauses during keypresses and resumes when the renderer is idle:
import { scheduleTask } from '@solidtv/solid';

scheduleTask(() => warmImageCache(items), 'low');
scheduleTask(() => trackImpression(item), 'low');

Same behavior, but the animation stays smooth because the work happens in the gaps between key presses instead of on top of them.

This is also why the Lazy primitives from section 2 take a delay option. After a keypress, the next node isn't created immediately; it waits for the animation to finish first. Same principle: do the work before and after the animation, never during it.

4. Memory: GC stutter and slow leaks

What it looks like: Two patterns. Sawtooth: animations are smooth, then hitch for ~200ms, then smooth again, every few seconds during heavy scroll. That's the garbage collector pausing the render loop to reclaim allocations. Slow climb: the app works fine for 10 minutes, slows after 30, and reloads itself or OOMs after an hour. People often call this a "Samsung TV app memory leak," but every platform has the same failure mode. Samsung just enforces stricter per-app limits, so it surfaces there first.

SolidJS and the SolidTV Renderer have been heavily optimized to avoid GC. Objects are reused wherever possible, especially inside the render loop, so most of the pressure in a SolidTV app doesn't come from the framework.

The biggest source of garbage is almost always the API layer. When you get a response back from your backend, don't convert it into other objects. Use the original response shape directly where you can, and if the shape isn't workable, ask the API team to adjust the response rather than mapping it on the client. A 200-item content rail that runs response.items.map(item => ({ id: item.uuid, name: item.title, ... })) on every fetch generates a fresh object per item, with no reuse. That's the kind of pattern that triggers a major GC mid-scroll on a TV CPU.

How to verify it

Both patterns show up in Chrome DevTools. The Chrome team has a thorough walkthrough at Fix memory problems. The short version:

  1. Open Chrome DevTools → Performance and record during the bad interaction.
  2. Watch the Memory graph. A rhythmic sawtooth is GC pressure. A line that climbs and never comes back down is a leak.
  3. For leaks, switch to the Memory panel and diff heap snapshots taken before and after the suspect flow to see which objects survived.

Common leak sources

A few patterns that come up in audits, in rough order of frequency:

If you've cleaned up the API layer and the sawtooth persists, the allocations are coming from the renderer itself. See Boosting LightningJS FPS by 50% for the renderer-level work that addresses it.

Step 0: Get a Number Before You Start

None of the fixes above matter if you can't measure the problem. "Feels slow on the box" doesn't get fixed.

The fastest way to find out what your TV is actually capable of is to load the SolidTV benchmark demo on the device itself. It's a standardized SolidTV scrolling and animation workload, so the FPS it reports is a clean ceiling for the hardware: if even the benchmark can't hold 60 on your target TV, that's the floor your own app is fighting against. If the benchmark hits 60 and your app sits at 28, the problem is in your code, not the device.

Once you have that baseline, drop SolidTV's <FPSCounter> into the corner of your own app for ongoing measurement:

import { FPSCounter, setupFPS } from '@solidtv/solid/primitives';
import { renderer } from '@solidtv/solid';

setupFPS({ renderer });

<FPSCounter mountX={1} x={1910} y={10} />

Now you know whether idle FPS is 60, 45, or 28, and which interactions tank it. Then attach the Chromium remote debugger (Tizen, WebOS, Fire TV, and Android TV all expose one) and capture a 5-second performance recording during the bad interaction. You'll almost always see one of: long JavaScript tasks blocking the render thread, the GC sawtooth from section 4, or slow paint times from too many shader switches or oversized textures.

The Diagnostic Checklist

When a TV app feels slow, regardless of which platform surfaced it, work through this in order:

  1. Mount an FPS counter on the device. Get a number.
  2. Profile on the real hardware, not the simulator and not your laptop.
  3. Virtualize long lists with LazyRow or VirtualRow.
  4. Defer non-critical work with scheduleTask.
  5. Audit the API layer for allocations: avoid re-shaping every response into new objects; use the original response where you can.
  6. Size images for the screen, not the source asset.
  7. If you've done all of the above and it's still slow, buy your customer a new TV.

SolidTV bakes most of this in by default. It's the framework I wanted while I was debugging stuttery Lightning apps in production. If you'd rather not assemble the toolkit yourself, start here.

Struggling with TV performance?

Schedule a free performance audit and we'll diagnose your app on a real device. No commitment, no slide deck.