Development/Engineering

Meeting React Again, Outside the Browser

A frontend developer on the factory floor discovers that React’s original question, how to keep a screen in sync with constantly changing state, fits device screens better than it fits web documents.

By Sogo Kim 2026.10.01 16 min read
frontend · react

React Beyond the Browser Tab

I used to work full-time at a startup, building web services mostly in React. Now I’m a contract developer working on-site at large manufacturers, moving from one company to the next. I assumed only my title and contract type had changed, and that I’d keep writing much the same kind of code. Instead, as I’ve been exposed to parts of the manufacturing world I’d never seen before, I’ve realized that JavaScript (and TypeScript) reaches much further than I knew.

Monitoring screens that show equipment status. Touch panels that workers press while wearing gloves. Gateway services that collect signals from devices and pass them on to a display. Few of these are what we normally call websites, yet many of them run on web technology, built with the same languages and tools we use for web UIs.

In my previous post, I asked whether every website really needs React. Looking at all this, I’ve come to think the word “website” is too narrow if it only means HTML documents on the web.

What “Frontend” Used to Cover

“Frontend” originally just meant the side that faces the user: the client, the front end of the system. But as the web became the dominant platform, that “front” seems to have hardened into “the browser viewport.”

Say “frontend developer” and most people picture someone building web pages. Since most frontend developers do exactly that, the assumption feels natural. My current workplace is no different. The FE team handles web screens only. I build screens for mobile and various kinds of equipment, but I’m on a separate Native team. We do the same job, building screens, under different names and different org charts.

Think about where people actually meet software: an ordering tablet on a restaurant table, an in-home wall panel linking the front door to the living room, a pay station at a parking garage exit, a hospital check-in kiosk, a self-checkout lane, a control panel next to a production line. What these have in common is that nobody types anything into an address bar. You power the device on and a fixed screen appears. You do a few things on it, the machine responds, and it shows you its new state. You don’t feel like you’ve visited a website. You feel like you’ve operated a machine.

Plenty of developers must build screens like these, and yet it strikes me as odd that the word “frontend” has come to mean, almost exclusively, screens inside a browser viewport.

Screens That Operate Machines, Not Documents

A web document and a device screen solve fundamentally different problems. The basic unit of the web is a document identified by a URL, so questions like search visibility, link sharing, time to first byte, and whether the initial HTML contains the content all matter. The server rendering, streaming, server components, and cache layers I wrote about in my last post are problems that grew out of this document model, along with their solutions.

Now take an ordering tablet. What matters there isn’t an address like /menu/coffee/americano. It’s whether the customer is browsing the menu, choosing options, talking to the payment terminal, or looking at an order number after paying. No search crawler will ever visit this screen, nobody will share a link to it, and there’s no reason to worry about filling the initial HTML for SEO. Startup time still matters, since the screen should be ready soon after power-on. Beyond that, though, the screen has to respond the instant it’s touched, reflect state changes from servers and devices without delay, and keep working after weeks without a restart.

So even with the same React, the central challenge on the web has been “how do we show what the server already knows, quickly?” On a device, it becomes “how do we consistently project constantly changing state onto the screen?” And that second question, interestingly, is the one React was created to answer.

To see why, it helps to look at where React came from. It was first presented at JSConf US in May 2013, after already being used inside Facebook, with Instagram following. In an interview with ACM Queue, Pete Hunt, an early member of the React team, said the most complex web app at Facebook was the tool for creating ads and managing ad accounts. He recalls that when that screen was built on Bolt, a system that watched for DOM changes, the team accumulated bugs nobody could figure out. I’d guess it was less a document to read than a tool you keep typing values into, checking, and manipulating.

React’s answer, as Hunt described it in a 2013 interview, was to let you think of the app as re-rendering the whole screen every time the data changes, like hitting refresh in a server-rendered app. Instead of telling the framework exactly what to change, you declare what the screen should look like for the current state, and React works out what actually changed by comparing internal representations known as the virtual DOM.

In other words, React’s first question wasn’t “how fast can we show this document?” It was “how do we keep constantly changing state and the screen from drifting apart?” That is much closer to what an intercom or an ordering tablet needs than to what a search-driven web document needs. So I don’t think it’s a coincidence that React feels at home on device screens. It’s close to where React’s design started.

The difference gets even clearer once you look past the screen at the network. In a typical web frontend, the network model you deal with directly revolves around HTTP requests and responses. Browsers and infrastructure abstract away most of the lower layers (DNS lookups, TCP or QUIC connections, TLS), so application developers mostly work with status codes, headers, caching, authentication, and CORS. There are long-lived connections on the web too, such as WebSockets and SSE (server-sent events, where the server keeps streaming events to the browser over HTTP), and failures and reconnection are things web apps have to design for as well. Still, a typical web app rarely makes you think about the transport layer itself, at least compared with talking to equipment.

On networks where machines talk to machines, nearly all of those assumptions flip. Your counterparts are sensors, gateways, and other devices rather than a single server, and there’s no single protocol like HTTP. Depending on the purpose, you might use Modbus RTU over a serial line, Modbus TCP over Ethernet, the industrial standard OPC UA, or publish/subscribe MQTT.

With Modbus, you handle register addresses and byte order yourself. When a 32-bit value is split across two registers, the order varies by manufacturer, so you read the datasheet and work out what the values mean. MQTT leaves the payload format up to your application, while OPC UA sits at a much higher level, attaching semantic information to data and modeling it. Communication styles differ too: Modbus has a clear request and response, whereas MQTT keeps a connection open to a broker and passes asynchronous messages through publish/subscribe. Some TCP-based protocols hold connections open for a long time, and serial links have no notion of a TCP-style connection at all.

In every case, a cable coming loose or a device rebooting is not an exception but a routine event. That’s why knowing how to notice that the other side is gone matters so much: heartbeats and timeouts, reconnection policies, MQTT QoS levels, Last Will messages. The network is often a closed one rather than the public internet, so you may also need to think about private certificates, fixed IPs, and time synchronization. Browsers can’t freely open raw TCP sockets or serial ports (a limit of their security model), so it’s common for a gateway to translate the device’s language into HTTP or WebSocket instead of having the screen talk to the device directly. The quality of that translation determines how far you can trust the state shown on screen.

A quick explanation for anyone unfamiliar with these names. Modbus is an old protocol from 1979 with a very simple question-and-answer structure: one side asks for the value of a particular register on a particular device, and the other replies with a number. A register is just a numbered storage slot for a small number, and whether that number is a temperature or a pressure isn’t part of the protocol. It’s listed in a table in the manufacturer’s datasheet. The same exchange over a serial line such as RS-485 is Modbus RTU; over TCP on Ethernet, it’s Modbus TCP.

OPC UA is an international industrial standard (IEC 62541) that lets equipment and software from different manufacturers exchange data. Instead of numbered slots, it represents data as objects carrying semantic information such as names and data types, and it includes authentication and encryption in the specification. MQTT is a publish/subscribe messaging protocol. A sensor publishes a value to a topic on an intermediary server called a broker, and any screen or service subscribed to that topic receives it. The two sides never need to know each other’s addresses, and its headers are light enough to work well on unreliable networks.

The rest of the terms: a heartbeat is a periodic “I’m still alive” signal, and when it stops, the other side is assumed to be gone. MQTT’s QoS levels let you choose how strongly delivery is guaranteed: at most once (0), at least once (1), or exactly once (2). A Last Will message is one a device leaves with the broker when it connects; if the device drops off abnormally, the broker publishes it on the device’s behalf so the screen can show that the device has disappeared. A VLAN divides one physical network into several logical layer-2 (data link layer) networks, and traffic between VLANs needs routing and its own policies. NTP keeps device clocks in sync. If clocks drift, you can’t even tell whose record came first, which makes it a more important setting on site than you’d expect.

On the web, a slow response is mostly handled with a spinner and a retry button. On a device, “no response” is itself a state. If you don’t define it, the user staring at a frozen screen can’t tell whether the machine is broken or the connection dropped. That’s exactly why React’s idea of computing the screen from state matters even more on device screens.

The Screen as a State Machine

When React debuted at JSConf US in 2013, its central claim was: don’t manipulate the screen directly; compute it from state. That idea connects with older efforts to model UI state explicitly, such as the statecharts David Harel described in his 1987 paper “Statecharts: A Visual Formalism for Complex Systems.” On the web, Facebook’s Flux (2014) popularized unidirectional data flow, and Redux, which followed, borrowed ideas from the Elm architecture as well.

This model feels especially natural for field devices, and an intercom is a good example. The screen has a finite set of states: standby, incoming call, in a call, opening the door, network disconnected. Each state allows different actions and shows different things. With TypeScript’s discriminated unions, that structure turns almost directly into code.

type IntercomState =
  | { kind: "idle" }
  | { kind: "ringing"; visitorId: string }
  | { kind: "talking"; visitorId: string; startedAt: number }
  | { kind: "doorOpening"; visitorId: string }
  | { kind: "offline"; retryAt: number };

function Screen({ state }: { state: IntercomState }) {
  switch (state.kind) {
    case "idle":        return <StandbyView />;
    case "ringing":     return <IncomingCall visitorId={state.visitorId} />;
    case "talking":     return <VideoCall visitorId={state.visitorId} since={state.startedAt} />;
    case "doorOpening": return <DoorProgress visitorId={state.visitorId} />;
    case "offline":     return <OfflineNotice retryAt={state.retryAt} />;
    default: {
      const _exhaustive: never = state;
      return _exhaustive;
    }
  }
}

The never check is the important part. When a requirement changes and a new state is added, the compiler flags every place that doesn’t handle it. Devices installed in the field aren’t easy to patch, so catching a forgotten state before deployment is less a convenience than a safety net.

Gathering state transitions in one place with useReducer, or declaring the allowed transitions with a state machine library like XState, lets you block impossible transitions much more systematically. And if you keep enough transition and event logs, you can trace and reproduce the sequence of events behind a fault in the field.

Component structure maps over well too. The camera view, call status indicator, numeric keypad, door button, and volume control are independent units of interaction on the physical device as well, so the component tree ends up mirroring the structure of the product almost one to one. I used to think making a component out of every paragraph of a blog was overkill. This match showed me a place where abstraction is a design tool rather than a cost.

Renderers as the Seam

React can step outside the browser because it separates computing what changed from applying those changes to a real environment. The reconciler works out what changed, and a renderer applies the result to the host environment. That split already existed when React Native was introduced in 2015, before Fiber. React 16 (2017) rewrote the reconciler as the Fiber architecture, and the react-reconciler package lets you build your own renderer on top of it. (The official repository, however, presents it as a package for experimental custom renderers.)

A renderer author implements a host config, which defines how to create nodes in the host environment, attach children, and update them. React handles everything else: state management, hooks, composition. That seam is why react-dom targets the browser DOM, React Native targets native iOS and Android views, react-three-fiber targets three.js scenes, and Ink targets text UIs in the terminal. Remotion goes further and uses React components to compose video frames.

For layout, I understand that both React Native and Ink use Yoga, a flexbox layout engine, so the flexbox intuition you built on the web carries over largely intact to mobile apps and terminal UIs.

React Native’s slogan at its 2015 introduction is telling, too: “learn once, write anywhere,” not “write once, run anywhere.” The promise wasn’t that the same code would run everywhere, but that the same way of thinking would work everywhere. React Native later made its New Architecture (Fabric, TurboModules, and more, built on JSI) the default from version 0.76. Hermes, its JavaScript engine optimized for mobile, had become the default engine earlier, in 0.70. Microsoft maintains separate React Native versions for Windows and macOS, which suggests this approach isn’t limited to phones.

In the end, React’s core was never the DOM. It was the computation that turns state changes into a minimal list of updates, and the DOM was just the most famous of the hosts that can receive that list.

Machines Running Chromium

In the field, the setup I run into most often isn’t actually React Native. It’s a machine with Chromium inside. Many kiosks, signs, and panel PCs next to equipment are web apps running in full-screen Chrome, Electron, or an embedded webview. (At my current workplace, I’m working with Electron.)

Electron started in 2013 as Atom Shell for GitHub’s Atom editor and was renamed in 2015. It bundles Chromium and Node.js so you can build desktop apps with web technology. It’s well known that Slack, Discord, and VS Code run on it, but fewer people realize the same architecture goes into dedicated single-screen devices.

TVs are similar. LG open-sourced Enact, a React-based framework for webOS TV, and, if I remember correctly, Netflix has written on its tech blog about building a separate rendering layer called Gibbon so it could use React for TV UIs.

The reasons web technology keeps spreading to devices are clear. The web is the most widely deployed UI runtime. The developer pool is huge. Designers and developers can speak the same language (HTML and CSS). And pushing a UI update over the network is far lighter than reflashing native firmware. Jeff Atwood’s 2007 line, “any application that can be written in JavaScript, will eventually be written in JavaScript,” was half joke and half prophecy. After working in manufacturing, I no longer hear it as a joke.

TypeScript Behind the Screen

It isn’t only the screens that are written in JavaScript. Between the equipment and the display sits a layer that collects, processes, and forwards data, and Node.js and TypeScript are surprisingly deep in that layer too.

Node-RED, which began at IBM and is now maintained under the OpenJS Foundation, is a JavaScript-based, flow-based tool widely used to wire up data flows in IoT and industrial settings. MQTT (created at IBM in 1999, an OASIS standard since 2014) has clients like mqtt.js that run in both the browser and Node, and OPC UA (IEC 62541) has implementations such as node-opcua. Node-RED itself reportedly began as a small project to visualize data connections between MQTT topics. WebSocket, standardized as RFC 6455 in 2011, is the standard way to push this kind of real-time data all the way to a browser or webview.

I think the real benefit isn’t using one language everywhere; it’s being able to share a data contract. If you define device message formats as both a runtime schema (with something like Zod) and TypeScript types, and keep them in a shared package, the gateway and the screen read the same definition. When the meaning of a single field changes in the field, the compiler and schema validation together show you everywhere the change reaches. TypeScript types don’t check data at runtime, so if you want to trust values coming from equipment, you need runtime validation as well. Where teams using different languages once met at a boundary, schemas and types now do the job that documents and verbal agreements used to do.

Grafana, the best-known monitoring UI, is a prominent open-source project with a React and TypeScript frontend, which suggests to me that this combination suits screens that handle industrial data.

The Field Is Harsher Than a Browser Tab

Of course, web technology reaching devices doesn’t mean web development instincts carry over unchanged. A typical consumer web page gets used briefly and closed. A device screen may run for weeks or months without being shut down, so a small memory leak or a duplicated event listener may not show up until the screen freezes days later. (A memory graph at 3 a.m. doesn’t lie.)

Disconnection is the normal state of the network, so you have to design what to show offline and how to resync after recovery. On touch screens with no hover and no keyboard shortcuts, interaction rules need rethinking from scratch: button size and spacing, preventing accidental taps, returning to the home screen when idle. Putting a full Chromium on a low-spec device is not a cheap choice in memory and boot time, so on resource-constrained hardware, a native approach can still make sense. Other declarative UI toolkits like Flutter are expanding toward devices as well.

Most important is the line around control loops. Safety-critical real-time control that actually starts and stops equipment belongs to the equipment’s dedicated controller. Control that needs deterministic responses shouldn’t be handed to a general JavaScript runtime with garbage collection. I try to always keep that distinction in mind: the screen shows state and delivers commands; it isn’t the layer responsible for safety.

So I’m not saying React is the best choice for modern UI development, or that it’s time for the age of React to end. I’m saying only that the range of problems React fits is far wider than I thought.

A Document to Read, or a Device to Operate?

What wore me out before wasn’t React itself. It was the atmosphere of treating application architecture as the right answer even for showing a simple web document, a mismatch between the size of the problem and the size of the tool. I still believe a clinic’s or a small business’s simple intro page doesn’t need a hydration strategy. But for a screen where state changes all day, people touch it constantly, and hardware and servers take turns pushing state into it, the same tool carries a completely different weight.

So these days, before choosing a technology, I ask one question: is this screen closer to a document to be read, or to a device to be operated? Many products, like online stores, are a mix, so there’s rarely a single right answer. But just asking which side dominates narrows down choices like SSR, server components, state machines, and offline strategy much faster.

Comments

No comments yet. Be the first to leave one.

Leave a comment

Comments appear after approval.