Performance

Why Your React TV App Is Slow (and Why Preact Won't Fix It)

By Chris Lorenzo · Published Oct 16, 2026 · 11 min read

A team that recently found SolidTV described the path that led them here, and I hear versions of it all the time. They built their TV app in React and traced the slowness to the virtual DOM and the overhead of DOM nodes. They tried Preact, and it barely helped. They moved to SolidJS, which was better, and then went looking for something built specifically for TVs.

Every step on that path is reasonable, and each one fixes something real. But on an older TV, the biggest cost isn't in any of those frameworks. It's in the browser underneath them: the work it does to turn HTML and CSS into pixels, on a slow CPU, in a version of Chrome that's years behind the one on your laptop.

What the browser does with every change

Chrome's team wrote a great walkthrough of this in Inside look at modern web browser (part 3), and it's worth reading in full. The short version: every page goes through a pipeline, and most of that pipeline runs on a single main thread.

HTML → DOM→ Style→ Layout→ Paint→ Composite

The article makes the point that matters most for TVs. Each stage feeds the next, so a change early in the pipeline means redoing the later stages for whatever it touched. And because style, layout and paint share the main thread with your JavaScript, all of it has to fit in about 16.7 milliseconds if you want 60 frames a second.

Now think about what a TV UI does all day. Moving focus one tile to the right changes a class or a style on two elements. The browser recalculates style for them and anything whose selectors depend on them, lays out what changed, repaints it, and composites the result, while the row's scroll animation is asking for the next frame. A home screen with a dozen rows of posters, titles, badges and gradients can easily run past a thousand DOM nodes, each with a computed style and a box in the layout tree.

Where React, Preact and SolidJS fit

Frameworks sit on top of that pipeline. They decide how much JavaScript runs before the browser's work starts, and how many DOM changes they hand over.

Work per update React Preact SolidJS (DOM) SolidTV
Virtual DOM diff Yes Yes, lighter No No
DOM changes Yes Yes Only what changed No DOM
CSS style calculation Yes Yes Yes No, styles are plain properties
Layout Browser Browser Browser Flex in JS, only for flex nodes
Drawing Browser paint Browser paint Browser paint WebGL draw calls

Moving from React to Preact to SolidJS shrinks the first row. The rest of the table belongs to the browser, and it doesn't change when you change frameworks.

Old Chrome on a slow CPU

Two things make that pipeline far more expensive on a TV than on your laptop.

The first is the hardware. Smart TVs ship with low-power processors chosen for cost. The older LG TV I test SolidTV on has two CPU cores and a 720p panel, and that's the main thread your app, your framework and the browser's whole rendering pipeline have to share.

The second is the browser. Both LG and Samsung tie the web engine to the platform version, so the browser in a TV is the one its platform shipped with. Here's what their developer docs list for each year:

Year LG webOS Samsung Tizen
2016 webOS 3.x: Chromium 38 Tizen 2.4: WebKit
2017 webOS 3.x: Chromium 38 Tizen 3.0: Chromium 47
2018 webOS 4.x: Chromium 53 Tizen 4.0: Chromium 56
2019 webOS 4.x: Chromium 53 Tizen 5.0: Chromium 63
2020 webOS 5.x: Chromium 68 Tizen 5.5: Chromium 69
2021 webOS 6.x: Chromium 79 Tizen 6.0: Chromium 76
2022 webOS 22: Chromium 87 Tizen 6.5: Chromium 85
2023 webOS 23: Chromium 94 Tizen 7.0: Chromium 94
2024 webOS 24: Chromium 108 Tizen 8.0: Chromium 108
2025 webOS 25: Chromium 120 Tizen 9.0: Chromium 120
2026 webOS 26: Chromium 132 Tizen 10.0: Chromium 130

Sources: LG webOS TV web engine and Samsung web engine specifications. LG lists release years for each webOS version.

Every LG and Samsung TV platform from before 2021 ships Chromium 69 or older. LG's 2016 and 2017 platform, webOS 3.x, runs Chromium 38, which came out in 2014.

Chromium has changed a lot since then, and much of that work went straight into the pipeline above:

A TV on Chromium 69 or older has none of it. Chrome's rendering lead describes RenderingNG as an architecture that "greatly outperforms what came before." Many of your users' TVs are running what came before.

So the same React app pays twice on a TV: a slower CPU runs each step, and an older engine does more work per step. Profiling in desktop Chrome hides both.

Skip the DOM entirely

This is why LightningJS, and SolidTV after it, render with WebGL instead of HTML. SolidTV keeps its own tree of nodes, much like the DOM, but each node is a plain object with properties such as x, y, width, height, color and alpha. There are no selectors to match and no cascade or inheritance to resolve. Layout is a small flexbox implementation in JavaScript that only runs for nodes using display: flex; everything else is positioned directly. The renderer then draws the whole scene into a single canvas with WebGL, so the browser has one element to composite instead of a thousand to style, lay out and paint.

Signal changes→ Update node properties→ Flex layout (if needed)→ WebGL draw

It isn't free. The renderer's work runs on the main thread too. The difference is that it's a small, purpose-built job instead of a general-purpose engine that has to handle the whole web. Because SolidTV owns that job, it can keep making it faster, like the same app going from 30 to 60+ FPS on a XumoTV when the upstream Lightning 3 renderer was swapped for SolidTV's. There are more numbers on the LightningJS alternative page.

For a React team, the code won't feel foreign. SolidTV uses JSX and components, with SolidJS signals in place of hooks. A focusable poster looks like this:

const posterStyle = {
  width: 185,
  height: 278,
  borderRadius: 12,
  scale: 1,
  focus: { scale: 1.1 },
  transition: { scale: true },
};

const Poster = (props) => (
  <view style={posterStyle} src={props.image}>
    <text y={290} fontSize={24}>{props.title}</text>
  </view>
);

The SolidTV basics tutorial walks through routing, remote-control focus, layout and signals from there.

We measured it: WebGL vs hand-written HTML

To put numbers on this, I built the same screen two ways and ran both on two LG devices. The screen is the TMDB rows page from the SolidTV demo app: a hero row and eight rows of posters. One version is SolidTV with its WebGL renderer. The other is the same screen written by hand in plain HTML and CSS, with no framework at all, and it follows the usual performance advice: CSS flexbox for the rows, and scrolling and focus done as CSS transform transitions that the compositor animates off the main thread.

Each version got five fresh launches per device and 40 simulated remote presses moving through the rows, with identical data served from a laptop on the same network. The results are medians.

An older LG TV: webOS 3, Chrome 38, two CPU cores, a Mali-400 GPU and 680 MB of memory.

Measure SolidTV (WebGL) Hand-written HTML
Frames shown during a move 27 FPS 24 FPS
Key press to screen 164 ms 887 ms
Main-thread frames 36.8 FPS 6.0 FPS
Runs that froze 0 of 5 3 of 5, for 3.7 to 15.8 s
Elements on the page 36 370

A newer LG 4K smart monitor: webOS 9.1, Chrome 108, four CPU cores.

Measure SolidTV (WebGL) Hand-written HTML
Frames shown during a move 53 FPS 18 FPS
Key press to screen 30 ms 173 ms
Main-thread frames 59.5 FPS 35.6 FPS

Frames shown during a move counts the frames actually presented in the 300 ms after each press, from DevTools traces. Key press to screen runs from the press to the first frame that shows it, also from traces; tracing slows the older TV down, so compare the ratio there, not the milliseconds. Main-thread frames is the page's own requestAnimationFrame rate. The HTML page animates on the compositor, so that rate understates its motion; the frames shown during a move are the fair comparison.

On the older TV, the HTML page's animations were nearly as smooth as WebGL's once a move had started. The compositor did its job. But the page took about 0.9 seconds to start showing each key press, against 0.16 for WebGL, and in three of its five runs it stopped producing frames entirely for between 3.7 and 15.8 seconds.

The traces show where that time went, and it wasn't JavaScript, style or layout. The HTML page's main thread was mostly idle, and style and layout took 3 to 4 ms a frame. The time went to the back half of the pipeline: painting, rasterizing tiles in software on two worker threads, compositing, and uploading the tiles to the GPU as textures, all competing for a two-core CPU. The WebGL version spent its frame in about 23 ms of JavaScript, the renderer and the app, and almost nothing else: no style, layout, paint or page rasterization.

On the newer monitor, HTML did much better: 36 frames a second and 0.17 seconds to answer a press. WebGL was still well ahead. One thing counts against HTML there: the monitor has a 4K panel, so Chrome rasterizes the HTML page at 3840×2160 while the WebGL canvas draws at 1080p. Forced to draw at 4K, WebGL dropped to 30 frames a second, but it still answered a press in 56 ms and showed more frames during a move than HTML (23 against 18).

HTML did win one thing: startup. The hand-written page, a single 15 kB script, had its first screen complete in 1.6 seconds on the older TV. The SolidTV demo app took about 11 seconds. That came from the demo app's legacy build for Chrome 38, not from WebGL: it loaded about 1.1 MB of JavaScript at startup, much of it polyfills replacing things Chrome 38 already has. JSON parsing, for example, ran in JavaScript instead of the browser's native parser.

A fix is on the way. It trims the polyfills to what Chrome 38 actually lacks, minifies the legacy bundle and changes how the renderer resolves image URLs. The renderer change is merged, and the demo app's build change is in review. On the same TV and the same screen, it brings the settled screen from 11.25 to 5.00 seconds and the frame rate from 37 to 43.5 frames a second. HTML still starts faster, but the gap drops from almost 10 seconds to under 3.5.

These are results for one demo screen on two devices, not a claim about every app or every TV. The HTML page is one reasonable implementation, written for this test and not tuned per device, and a leaner design could do better on the older TV. I've kept the full method, traces and per-run numbers. If you want to dig into them, get in touch.

When HTML is fine

None of this means HTML can't work on a TV.

If you stay on the DOM, the usual advice applies: keep the node count down, virtualize long rows, and animate only transform and opacity so the compositor can run the animation off the main thread. The Chrome article explains why compositor-only animations are the smoothest. Just know that the hand-written page in the test above did all of that, and on the Chrome 38 TV it still took close to a second to answer a key press.

But if your app has to support the TVs in that table, the browser's pipeline is the ceiling. You can optimize everything below it, but you can't optimize through it.

Where to go from here

If you have a React or HTML TV app that struggles on older models, I can help. A performance audit shows how much you can win back where you are, and a migration assessment shows what moving to SolidTV would take.

Further reading

Is your React TV app slow on older TVs?

Send the symptoms and the devices you support. A free consultation will tell you whether the fix is in your app, or whether the browser is the ceiling.