Phoenix Min
Slim down Phoenix to LiveView-only with inline HEEx templates
Phoenix Min is a post-installation tool that transforms a default Phoenix installation by removing controller-based infrastructure and converting all templates to inline HEEx using the ~H sigil.
Why Phoenix Min?
Phoenix is fantastic, but the default installation includes controller infrastructure that you might not need if you're building a LiveView-only application. Phoenix Min embraces:
- LiveView-first: All pages are LiveViews, no controllers needed
- Colocation: Templates live in the same file as behavior (no more
.html.heexfiles) - Simplicity: Fewer files, fewer directories, easier navigation
- Flexibility: Works with or without Ecto, assets, gettext, etc.
Installation
Option 1: One-Step Install (Recommended)
Create a new Phoenix application with Phoenix Min already applied:
mix igniter.new my_app --install phoenix_min --with=phx.newThis single command creates a new Phoenix application and immediately applies all Phoenix Min transformations.
Option 2: Add to Existing Project
Add phoenix_min to your Phoenix project's mix.exs:
def deps do
[
{:phoenix_min, "~> 0.1.0", only: [:dev], runtime: false}
]
endThen fetch dependencies:
mix deps.getUsage
Option 1: One-Step Install
Create a new Phoenix application with Phoenix Min in one command:
mix igniter.new my_app --install phoenix_min --with=phx.new
cd my_appIgniter will show you a diff of all the changes. Review and accept them.
Option 2: Add to Existing Project
After creating a new Phoenix application:
mix phx.new my_app
cd my_appRun the Phoenix Min installer:
mix igniter.install phoenix_minOr directly:
mix phoenix_min.installIgniter will show you a diff of all the changes. Review and accept them.
What Gets Modified
Phoenix Min makes the following transformations:
Files Created
lib/my_app_web/home_live.ex- LiveView with inline template replacing PageControllertest/my_app_web/live/home_live_test.exs- LiveView test
Files Modified
lib/my_app_web/router.ex- Root route changed fromget "/", PageController, :hometolive "/", HomeLive, :indexlib/my_app_web/components/layouts.ex- Converted fromembed_templatesto inlinedef root(assigns)anddef app(assigns)functionslib/my_app_web/controllers/error_html.ex- Converted to inlinerender/2functionstest/my_app_web/controllers/error_html_test.exs- Updated to test inline templates
Files Removed
lib/my_app_web/controllers/page_controller.exlib/my_app_web/controllers/page_html.exlib/my_app_web/controllers/page_html/(directory)lib/my_app_web/controllers/error_html/(directory)lib/my_app_web/components/layouts/(directory)test/my_app_web/controllers/page_controller_test.exs
What's NOT Modified
- Your dependencies (Ecto, Tailwind, etc.) remain untouched
- Core components stay as-is
- Your custom routes and controllers (if any)
- Your existing LiveViews
- Phoenix framework files
Example: Before & After
Before (Default Phoenix)
lib/my_app_web/controllers/page_controller.ex:
defmodule MyAppWeb.PageController do
use MyAppWeb, :controller
def home(conn, _params) do
render(conn, :home)
end
endlib/my_app_web/controllers/page_html.ex:
defmodule MyAppWeb.PageHTML do
use MyAppWeb, :html
embed_templates "page_html/*"
endlib/my_app_web/controllers/page_html/home.html.heex:
<div>Welcome to Phoenix!</div>Router:
get "/", PageController, :homeAfter (Phoenix Min)
lib/my_app_web/home_live.ex:
defmodule MyAppWeb.HomeLive do
use MyAppWeb, :live_view
def mount(_params, _session, socket) do
{:ok, socket}
end
def render(assigns) do
~H"""
<div>Welcome to Phoenix!</div>
"""
end
endRouter:
live "/", HomeLive, :indexInline Templates in Phoenix
Phoenix Min uses inline HEEx templates via the ~H sigil. Here's how to write them:
defmodule MyAppWeb.MyLive do
use MyAppWeb, :live_view
def mount(_params, _session, socket) do
{:ok, assign(socket, name: "World")}
end
def render(assigns) do
~H"""
<div class="container">
<h1>Hello, {@name}!</h1>
<.button phx-click="greet">
Say Hello
</.button>
<%= if @show_message do %>
<p>Message shown!</p>
<% end %>
</div>
"""
end
def handle_event("greet", _params, socket) do
{:noreply, assign(socket, show_message: true)}
end
endLayouts
Layouts are also converted to inline:
defmodule MyAppWeb.Layouts do
use MyAppWeb, :html
def root(assigns) do
~H"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<.live_title>{@page_title}</.live_title>
</head>
<body>
{@inner_content}
</body>
</html>
"""
end
def app(assigns) do
~H"""
<header>
<nav>Navigation here</nav>
</header>
<main>
<.flash_group flash={@flash} />
{@inner_content}
</main>
"""
end
endPhilosophy
Phoenix Min is opinionated about LiveView-first development:
- Everything is a LiveView: Even static pages benefit from LiveView's live reload and future interactivity
- Colocation > Separation: When templates are small and specific to one LiveView, keeping them inline reduces cognitive overhead
- Less is More: Fewer files and directories mean faster navigation and less context switching
- You're in Control: Phoenix Min only modifies generated files, never your dependencies or custom code
Compatibility
- Phoenix: 1.7.x and 1.8.x
- Elixir: 1.15 or later
- Works with:
-
Ecto or
--no-ecto -
Tailwind or
--no-tailwind -
Esbuild or
--no-esbuild -
Gettext or
--no-gettext -
LiveDashboard or
--no-dashboard
-
Ecto or
FAQ
Can I still use controllers?
Yes! Phoenix Min only removes the default PageController. You can still generate controllers with mix phx.gen.html or create them manually. Your existing controllers are not touched.
What about core_components.ex?
Phoenix Min leaves core_components.ex untouched. It's a large file with many components that you'll use across your app, so keeping it as-is makes sense.
Can I convert my existing LiveViews to inline?
Yes! Simply move the content from your .html.heex file into a render/1 function with the ~H sigil, then delete the template file.
What if I already deleted PageController?
Phoenix Min gracefully handles missing files. It will only modify what exists in your project.
Does this work with umbrella apps?
Yes! Phoenix Min detects umbrella apps and operates on the _web application.
Can I undo these changes?
While there's no automated "undo" feature, all changes are shown in a diff before you accept them. You can review and reject any changes you don't want.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License - see LICENSE file for details.
Credits
Built with Igniter - the code generation and project patching framework for Elixir.