Published on March 4, 2025
EAEmil Ahlbäck
In the past three weeks, I developed and launched one of the most technically challenging and exciting features I've ever worked on at Lovable, our AI-powered full-stack app-building platform. Lovable users can already generate a fully functional application by just prompting our AI (x.com). As great as Lovable already was for instantly generating apps, we realized developers and designers often want more precise control over the final touches.
To solve this, I set out to build what became the Visual Edits feature: an intuitive, Figma-like visual editor that empowers users (regardless of frontend skill) to visually select, edit, and save instant frontend changes directly in their apps.
This post explores how Visual Edits is engineered, why it was a particularly exciting technical challenge and why solving complex frontend UX problems is crucial to our mission at Lovable of transforming how apps are built.
Traditionally, UI development cycles follow a painful loop: we tweak CSS, click save, wait for rebuild, refresh browser, notice mistakes, adjust again... iteration is slow, frustrating, and error-prone. AI generation is amazing for fast kick-offs, but when you need to polish your frontend, precise iteration becomes crucial:
AI costs continue to drop, but they're still expensive. Especially when it needs context on the full app even for small changes.
Each regeneration costs both time and money - for us running the infrastructure and for users waiting for results. Visual Edits dramatically reduces these costs by allowing precise, targeted changes without touching the AI at all.
This makes it cheaper to create great products with greater precision.
We decided to move part of the code layer closer to the visualization layer in-browser, creating a WYSIWYG editor. Often WYSIWYG tools abstract away code entirely. Our approach instead keeps the direct, bi-directional connection between visual edits and the underlying source code. Here's how it works under the hood:

When you start a Lovable project, an ephemeral dev server spins up instantly in the cloud. How this works is worthy of a whole post in itself. Basically, we continually host 4k+ instances on fly.io to serve Lovable projects, and at compile-time each JSX component our AI generates is tagged with a unique, stable ID using our custom Vite plugin. These stable IDs can persist across visual changes.
Our infrastructure orchestrates these containers in clusters across multiple regions, with each container running an isolated Node.js environment containing a full copy of your application code. This approach allows us to scale horizontally based on demand while maintaining consistent performance regardless of project complexity or user location.
When you visually select any DOM element in the app, we instantly trace it back to the exact JSX responsible for rendering it. This ensures precise bi-directional mapping:
We sync the project's code entirely into the browser, representing it as an Abstract Syntax Tree (AST), essentially an interactive, live data structure reflecting the entire application structure. Babel and SWC are both great libraries for this. There's a ton of cool things we can do once we have the AST client-side. For example:
Theoretically, one could rely on Regexes for this functionality (which is kind of similar to how AST parsers work under the hood), but having the actual AST available allows us to make far more robust and maintainable code.
For example, consider updating a component's styling. Without AST parsing, we might resort to dangerous regex replacements:
test
This approach fails with nested components, complex JSX, or when components use dynamic expressions. With AST parsing, we can safely traverse the component tree and make precise modifications:
test
It also makes some otherwise difficult things very easy, like extracting a JSX tree of all components:
test
When you visually tweak a component by for example adjusting Tailwind classes, these changes are optimistically applied by a client-side Tailwind generator that intelligently reads your custom configurations. Even before hitting save, your visual edits are previewed exactly as they'd appear post-save, thanks to client-side AST mutations and instant Tailwind parsing.

Thanks to Vite and our persistent Dev Servers, we can immediately trigger a Hot Module Replacement (HMR), refreshing the preview without ever needing an explicit page reload. Upon clicking save, a chain of events kicks off instantly:
This round-trip, from intuitive visual interaction to production-grade code, happens seamlessly in seconds - enabling developers and designers to iterate at speeds previously impossible without losing control over code quality.

Speeding up the iteration cycle isn't just about convenience—I've personally felt firsthand how it transforms the way we build products:
On the technical side, bringing Visual Edits to life pushed me to deeply integrate complex technologies like client-side AST handling, dynamic in-browser compilation, custom code generation, rapid cloud deployments, and fine-grained React component controls. If you're an engineer who loves tackling challenging, real-world problems, building this feature at Lovable has felt like the ultimate playground.
Launching Visual Edits has opened up some exciting possibilities. I'm already imagining how we can build on this foundation in the near future:
I'm really looking forward to seeing the impact these ideas will have on the experience of building apps as a whole.
I really enjoyed working on this, and we're currently hiring. So contact me if this sounds exciting to you and you want to join us.
Also check us out at Lovable.dev or drop me a message directly if you'd like to learn more.