← All posts
The frontend you do not write

The frontend you do not write

A marketplace listing page is the ordinary test of a modern stack. Type in the search box and results narrow. Change the price filters and the list updates. Pick a different sort order and the rows reorder. The URL keeps up, the back button works, and the page never blanks out.

That is normally a client application: a component tree, a state store, a fetch layer, a router, and a serialisation format between the browser's idea of a listing and the server's. In Phlo it is a route that returns a view, and there is no client application at all.

The route already knows what changed

Mark a form <form.async> or a link <a.async> and the engine intercepts the submit or the click, posts to your route, and patches the DOM with what comes back. The route decides what should happen and says so:

route async POST listings filter {
    $this->applyFilters(%payload)
    return apply(inner: ['#results' => $this->results], path: $this->filterUrl)
}

inner: replaces the contents of an element, path: updates the address bar without a navigation. There are more commands, outer, append, class, attr, value, remove, toast, css, scroll, but they are all the same shape: name an element, name what happens to it.

The browser side is a small interpreter that knows about elements and operations, and nothing about listings, prices or sellers. That is why it stays one file rather than growing into a framework: your domain never enters it.

No second model of your data

The expensive part of a client application is rarely the rendering. It is that the browser ends up holding its own version of your data, which then has to be kept in step with the server's version through an API designed for exactly that purpose. Two models, one truth, and a synchronisation problem you now own forever.

Here the markup that comes back is rendered by the same views that rendered the page originally, from the same records. There is nothing to keep in step, because there is only one copy.

The same channel carries progress. chunk() sends a DOM command mid-request, so a long import updates a status line while it runs, and a streamed model answer appends token by token, without a WebSocket or a polling loop.

What you give up, and what you get

You give up offline-first behaviour and optimistic UI, both of which genuinely need client-side state. If your product is a drawing tool or an editor that must work on a train, this is the wrong model and Phlo does not pretend otherwise.

What you get is that a feature is one route and one view. No API to design for your own frontend, no state library to learn, no build step between writing markup and seeing it. Realtime is additive on top: a cast() sends the same commands to every open screen, so a second browser watching the same page updates from the same code.

Where to read more

The views chapter covers the full command set and the apply() protocol; the routing chapter explains async, both and why a sync fallback still renders a full page.

We use essential cookies to make this site work. With your permission we also use analytics to improve the site.