kuon

kuon

~H sigil everywhere vs .heex files - any real world experience?

I’ve been rethinking how I organize templates in Phoenix apps. The standard approach (at least the scaffold and what I consider standard) is separate .html.heex files, but I’m leaning toward using ~H sigil for everything instead.

Why?

The mixed approach bothers me. You end up with implicit rules that nobody enforces: “small components inline (like core_components.ex), big ones (like a live view or a controller or even a toolbar) in files” - but what’s the threshold? “Reusable stuff gets files” - how reusable? These fuzzy boundaries makes me wonder every time I add something.

With all-~H, you get a predictable module structure. Logic and handlers at the top, all rendering at the bottom (at least that’s how I see it). You can scroll to the end of any LiveView and immediately see what it renders. No jumping between files.

More importantly, you can define function components directly inside your template. Need a small helper? Just write a function right there above the main ~H block. This keeps template-specific helpers colocated without polluting your module’s public interface or creating separate files for tiny pieces.

Cons:

Tooling. LSP features, syntax highlighting, formatters all work better with dedicated .heex files. That’s real friction (or is it?)

The other common argument is “designers need separate files.” But honestly, how many teams actually have non-Elixir people editing templates? And even with .heex files, you still need to understand assigns, components, and Phoenix conventions.

Any story that could help?

Has anyone actually done this at scale? I’m talking “old” (maintained, iterated), multiple devs, real production app. Did the tooling gap become painful? Did you regret it?

Or did anyone try this and switch back to separate files? What broke the camel’s back?

I’m less interested in theoretical arguments and more in “I tried this and here’s what happened.” The ecosystem pushes separate files more (now with embed_templates), but I want to hear from people who’ve actually tested the alternative.

Most Liked

garrison

garrison

Separate template files are really inherited baggage from Phoenix’s roots as an actual web framework rather than the app framework LiveView has grown into. Unfortunately this is not the only example of “web framework thinking” weighing LiveView’s app capabilities down.

In the JS world the React people figured out pretty quickly that templates are the enemy and must be done away with entirely. They inverted the model and built up the DOM in code rather than by gluing strings together (React.createElement('div', ...)) and then they built a DSL that made that approach pretty (JSX). Incidentally this approach was directly descended from a PHP framework called XHP. Unfortunately a lot of people do not understand this distinction and mistakenly believe that JSX is a template syntax.

HEEx was descended from EEx, which I assume came from ERB (Ruby), which in turn has a lineage back through JSP (Java), ASP, and then probably PHP. I wasn’t there, though, so who knows.

EEx is a templating language, but it does embed real Elixir code (as opposed to a degenerate sublanguage like Jinja). HEEx, by actually parsing and validating the HTML, was a good step towards “JSX nirvana” in that the templates were no longer just strings. And Elixir, being a functional language, actually lends itself more to this approach in that the template is a function and can evaluate like one.

Unfortunately HEEx has since grown a number of “DSL” capabilities (:if and :for) which are a complete 180 from this path. Frankly the more I think about it the more I am convinced this was a significant mistake.

LiveView, unlike HTMX and Hotwire and others, is actually an app framework - like React. It can be used to build stateful “real” apps which happen to be server-rendered. This is significantly different from web frameworks, which are tools used to glue a bunch of forms together.

App frameworks thrive on components rather than templates and views, which is why LiveView naturally grew them. (Unfortunately I think there may be some denial about the fact that those components need state, but I digress.)

In “app framework” world there is no distinction between “templates” and “code”, there is just code. JSX is just JS with fancy syntax. There is no template.

And so the question of separating the template is fundamentally moot. There should be no template.

This is the source of the dissonance you’re feeling.

10
Post #3
chrismccord

chrismccord

Creator of Phoenix

I almost never use separate heex files fwiw. It’s all the same, so do what feels best for you :slight_smile:

We try to strike a balance w/ the generators. The only exception for me is largely static or massive markup “pages” that don’t really make sense to collocate. Don’t overthink it :slight_smile:

jdiago

jdiago

I’m going to approach this from a different perspective.

I had an opportunity to jump on a small-ish project a little while ago where the og author did have separate heex files. Since that’s how the project was when I got there, I just followed that convention. Although, I usually started my own LiveView code without the extracted heex files.

What caught my attention from the OP was the mention of the friction of having to jump between files so I’m looking at this with tools in mind. The reason the original question doesn’t bother me either way is because I’m used to doing this:

In the first screenshot, I have the same big file open in 2 splits so I can see 2 different regions at the same time. In the second, I have the heex file in the top split and the LiveView module in the bottom.

This is just an example but I usually try to have a max of 3(maybe 4) splits in various layouts. The splits could share the same file or not, the way I interact with them is the same.

The example I show is my nvim setup. I also do the same in vscode and zed (and JetBrains stuff back when I used those).

Where Next?

Popular in Discussions Top

andre1sk
A big advantage to Elixir is all the distributed goodness but for many applications running on multiple nodes having integrated Etcd, Zoo...
New
WolfDan
After doing a port from a c++ library to my project in phoenix I’ve seen that I need a faster way to run this algorithm and I found this ...
New
jeramyRR
This is an interesting article to read. Elixir’s performance, like usual, is excellent. However, it seems like the high CPU usage is co...
New
laiboonh
Hi all, I am trying to convince my team to use liveview over the current react. What are some of the points where one should consider us...
New
thojanssens1
It would be nice to be able to define a redirect from one route to another from the router.ex file. E.g.: redirect "/", UserController, ...
New
pillaiindu
I want to convert a Phoenix LiveView CRUD website to a CRUD mobile app. What do you think is the easiest way to do so?
New
New
Qqwy
I would like to spark a discussion about the static access operator: .. For whom does not know: it is used in Elixir to access fields of...
New
pdgonzalez872
If this has been asked here before, please point me to where it was asked as I didn’t find it when I searched the forum. Maybe a mailing ...
New
slashdotdash
Phoenix Live View is now publicly available on GitHub. Here’s Chris McCord’s tweet announcing making it public.
New

Other popular topics Top

New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43806 214
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement