DEV Community

Cover image for The rendering engine: how a no-code project becomes a smooth native app
Mathieu Poli for GoodBarber

Posted on

The rendering engine: how a no-code project becomes a smooth native app

People picture no-code as snapping frozen blocks together. Let me open the hood on the part that actually matters: the rendering engine that turns a data model and a config into polished, native Swift and Kotlin.

I've spent fifteen years building these engines. It's what my teams at GoodBarber do all day. This piece is the tour I'd give you if you visited: what a rendering engine is, what happens between the user clicking Publish and a native app running on a phone, and the engineering problems that live in between.

What a rendering engine actually is

A rendering engine, in a no-code platform, is the layer that transforms a project's description, a data model plus a configuration, into a running application. Everything the user decided in the back-office (content structure, sections, navigation, design choices) exists as structured data. The engine is what makes that data become software.

The common misconception is that this layer is a theme: a fixed shell with slots where your content and colors get poured in. A theme customizes an app that already exists. An engine derives the app from the description. The difference shows up at the edges. A theme degrades as soon as your project deviates from what the shell anticipated. An engine re-derives: different data model, different sections, different navigation depth, and the output is still coherent, because the coherence is computed.

Here's the pipeline that does it.

From a data model to polished native code

Stage one is the input: a data model (the shapes of the user's content: articles, products, events, custom types) and a configuration (which sections exist, how they're arranged, every design decision expressed as parameters of a formalized design system).

Stage two, the engine resolves that description against its component catalog. The components have been designed, built and hardened once, then instantiated in every app the platform produces. The design system acts as the engine's grammar here: spacing, typography and color roles resolve from tokens, not from per-app hardcoding.

Stage three is the output: compiled Swift for iOS, compiled Kotlin for Android. Real projects, real binaries, submitted to real stores.

Why insist on that last stage? Because "it renders on a phone" and "it's a native app" are two different engineering standards. Native output means the app speaks the platform's own language. Scrolling with the system's exact physics. Transitions and motion that match the OS. Haptics where the platform expects them. Text that behaves like platform text. Users can't name these properties, but their thumbs find every one of them within seconds. And this polish is the part a plausible generated codebase skips, because it lives below the level a demo exercises.

One more thing about economics, because it's what makes the whole model work. This native-quality bar is paid for once, by my team, and amortized across every app the engine renders. One example from our own codebase. Our Home section is lists inside lists: a vertical list of widgets, where each widget is itself a scrolling list of content. Any iOS developer who has built that pattern knows the trap. Nested lists defeat the system's cell recycling, cells multiply, memory climbs, and the classic fix, cache more, is also the classic way to blow up. Our engine recycles cells across the nesting, from the inner lists up to the containing one, with widgets of very different natures drawing from the same pool. We shipped that Home page in 2017. Apple introduced a system tool for the pattern two years later (compositional layout), and it helps, but it doesn't end the work: we've been re-tuning that part of the engine continuously ever since, and we're not done. And that's one example. The engine is full of them. No individual project could afford that. None needs to.

What the engine deduces on your behalf

Here's what "derive" means concretely, because it's exactly what a theme can't do. A widget's description carries only what the user decided. Everything else is deduced.

The number of columns doesn't need to be in the description: one on a phone, two on an iPad, three in landscape, resolved at render time. Text direction is inherited from the app's global settings, unless this widget says otherwise. Configs written years ago still render, because the engine migrates old description formats on the fly.

Part of those deductions comes from the design system and its formalized rules. My favorite example is one line of code carrying a whole design opinion. When a small element inside a card, a play button, a date badge, has no shape of its own, the engine gives it the app's global shape token, with the corner radius capped at 8 points. Your brand's rounded corners propagate down to the tiniest badge, but never so far that the radius swallows the element. Nobody configures that. Someone decided it once, and the engine enforces it in every app.

The output isn't static, either. A rendered page ships with its behaviors: the header that retracts as you scroll, the transitions between screens, the gestures the platform expects. None of that sits in the description. It's code, written once, derived everywhere.

And when a configuration stops making sense, the engine doesn't obey it. It transforms it. The description of a detail page's header carries the list of buttons the user wants up there. Ask for more than three and the engine won't cram them in: it switches to a deployable toolbar, automatically, so the top of the screen stays breathable. That philosophy, prevent the mistake instead of rendering it, is one we've documented on our blog.
A caveat, because all my examples come from iOS: that's the engine I know line by line. Its Android and web siblings have their own versions of every one of these mechanisms.

Multiply all of that, the deductions, the behaviors, the transformations, by the sixty-or-so widgets in the catalog, and that's the difference between deriving and decorating.

The problems you never see in a demo

The pipeline is the architecture. The engineering, the part that consumes my teams' actual years, is a list of problems that never show up in a demo and always show up in production.

Lists that stay smooth at scale. Demos show twelve items, real apps have thousands. Cell recycling, image loading and downsampling off the main thread, pagination that doesn't stutter, prefetching that doesn't flood the network. Classic mobile engineering, unforgiving, and in an engine it has to hold for any data model a user defines, not for one hand-tuned screen.

Offline that tells the truth. Real usage happens in parking garages and airplanes. The engine caches content so the app opens and navigates without network. That drags in the hard questions: what's cached, for how long, how staleness is shown, what happens to actions taken offline. Solving this generically, for every content type the platform supports, is engine work in its purest form.

Consistency across the whole app. Ten sections built by different hands over months still have to feel like one product: same rhythm, same type scale, same behaviors. In hand-built projects this dies by a thousand small divergences. In an engine it can't, because every screen derives from the same tokens and the same components.

And the OS wave, every year. New OS versions, new device formats, new store requirements. Apps that aren't maintained don't stay the same, they age, visibly. Behind an engine, the adaptation happens once, centrally, and apps regenerate into the new world. A user who described their project three years ago has a current app today without having touched anything.

None of these problems is exotic. Any senior mobile developer will recognize all four. Which is the point. The engine doesn't make the engineering disappear. It moves it, from every individual project into one layer built by people who do nothing else.

Three engines, one description

One last thing the frozen-blocks picture misses completely. The same project description is consumed by three independent engines: Swift for iOS, Kotlin for Android, and a TypeScript/Angular engine that produces a Progressive Web App.

Architecturally, that means the user's project is genuinely abstract. Pure description, owned by no runtime. Each engine interprets it idiomatically for its world: the iOS app is fully an iOS app, the Android app fully an Android app, the PWA a real web citizen with URLs and SEO.

Take sharing. The description says: this content can be shared. On iOS, the action opens Apple's share sheet. On Android, the platform's own dialog. On the web, the browser's share API. Even the share buttons differ: we draw the icons ourselves, one per engine, each close to the codes of its platform, because an iOS user and an Android user don't picture the same symbol when they think "share". Same key, three renderings, each one correct in its world. Or take blur: the frosted effect behind an element doesn't blur the same way on iOS, on Android and in a browser, so each engine implements its platform's version of it rather than imitating another's. Three different renderings, and no divergence. It's the same sentence, spoken in three languages.

And the three engines never drift apart on what the app is, because none of them owns that information: they all read the same description. Teams that maintain an iOS app, an Android app and a web app by hand have to rebuild every feature three times, then keep checking that the three versions still match. Here, that matching isn't a discipline. It's a consequence of the structure.

So, is no-code real engineering? I'd flip the question. The point of the category is that the serious engineering happens once, below a surface simple enough that it doesn't have to be everyone's job.

Mathieu Poli — Head of Frontend Engineering @ GoodBarber. I teach and write about frontend engineering, product design, and AI — and everything that happens when the three meet. · X: @hellomathieup

Top comments (0)