ExInertia - Toolkit for Phoenix + Inertia.js Integration with Bun and Vite

I’m excited to announce ExInertia, a toolkit built on top of Igniter that provides a robust integration between Phoenix and Inertia.js, with first-class support for Routes library and Bun.

:wrench: Technical Stack:

  • Built on Igniter for powerful, composable installer generators
  • Full Inertia.js integration with Phoenix using Inertiajs/inertia-phoenix
  • Modern asset pipeline using Bun + Vite (replacing esbuild/tailwind)
  • Built-in Routes integration for type-safe routing between Phoenix and TypeScript
  • Automated manifest handling for Vite assets

:package: Installation:

def deps do
  [
    {:exinertia, "~> 0.5.0"},
  ]
end

The installer, powered by Igniter (thanks @zachdaniel ), handles the complete setup:

mix exinertia.install

:hammer_and_wrench: What gets installed:

  1. Vite manifest reader in your Web namespace
  2. Inertia pipeline + configuration in your Router
  3. Routes integration for type-safe routing
  4. Modified root layout with Vite asset handling
  5. Complete Bun + Vite setup replacing esbuild/tailwind
  6. TypeScript-ready frontend structure with Routes type definitions
  7. Automated mix aliases for asset building

:zap: Development Experience:

  • Hot Module Replacement (HMR) with Vite
  • TypeScript compilation with Bun
  • Type-safe routing between Phoenix and TypeScript using Routes
  • Seamless server-side rendering support
  • Zero-configuration Tailwind integration

Type-safe routing example with Routes:

// Your routes are automatically typed!
const url = Routes.path('user.show', { id: 123 });
// => "/users/123"

// TypeScript error if you miss required params
const url = Routes.path('user.show'); // Error: missing id parameter

The project is built to be modular - you can use the installers independently or compose them with your own Igniter-based installers.

:link: Resources:

Contributions and feedback are welcome! We’re particularly interested in hearing about different use cases and integration patterns.

19 Likes

Looks awesome!

FYI your package can be installed without modifying the mix.exs file by anyone who already is using igniter with mix igniter.install exinertia and that’s the command we recommend to use when running installers as opposed to calling exinertia.install directly :slight_smile:

Great work!

5 Likes

Thank you!

I actually wanted to contact you before I release this to get feedback about how I used Igniter.

For the installer part, I figured that since Igniter is relatively new, not a lot of people would be using it in their projects, and I aimed for having the users add only one dependency to get started.

But I’ll refine this moving forward to include your suggestion.

Ah, actually I just spotted this: exinertia/mix.exs at main · nordbeam/exinertia · GitHub

So one of the main reasons we suggest people work with igniter directly to install packages etc. is to ensure that igniter is a dev/test only dependency. Igniter depends on mix and having it as a production dependency causes issues.

So either exinertia itself needs to be a dev/test dependency, or it needs to make igniter an optional dependency. This lets end users list it as a dev/test dependency in their application.

Based on what it does I think either of those things is fine because exinertia doesn’t need to run in production for users right?

Small note, if you set only to only: [:dev, :test]in your installer’s info callback, then igniter will automatically install it with only: [:dev, :test], but only if installed via mix igniter.install exinertia

1 Like

Thanks for the feedback! I’ve updated exinertia to make Igniter an optional dependency and modified the README to instruct users to install via mix igniter.install exinertia. This should help ensure Igniter remains a dev/test only dependency as intended.

2 Likes

Awesome! One small note is that mix igniter.install actually works even when igniter is not installed into a project, as long as igniter_new is installed as an archive.

So your instructions could be

# in your project directory
mix archive.install hex igniter_new
mix igniter.install exinertia

And then the user doesn’t have to manually edit even a single file :slight_smile:

4 Likes

Thanks for creating this! I’ve been playing around with Inertia.js and Phoenix and have really been enjoying the experience. It was getting old having to go through all the same setup steps. I had a couple of questions:

  1. Are there any changes you need to make to the Dockerfile that is created by mix phx.gen.release --docker? I tried to deploy my app to Coolify on a self-hosted VPS. My build keeps failing during the builder stage at RUN mix assets.deploy. Here is what I see in the debug log:
#22 [builder 12/17] COPY assets assets
#22 DONE 0.0s
#23 [builder 13/17] RUN mix assets.deploy
#23 0.771 Compiling 17 files (.ex)
#23 1.753 Generated test_exinertia app
#23 2.003 03:56:23.754 [debug] Downloading bun from https://github.com/oven-sh/bun/releases/download/bun-v1.2.1/bun-linux-x64.zip
#23 4.794 $ ../_build/bun x --bun vite build --minify
#23 4.806 Resolving dependencies
#23 5.309 Resolved, downloaded and extracted [128]
#23 5.371 Saved lockfile
#23 6.361 failed to load config from /app/assets/vite.config.ts
#23 6.361 error during build:
#23 6.361 undefined
#23 6.375 error: script "build" exited with code 1
#23 6.381 ** (Mix) `mix bun build --minify` exited with 1
#23 ERROR: process "/bin/sh -c mix assets.deploy" did not complete successfully: exit code: 1

It looks like the Vite.js config file can’t be found. When I used Svelte in a Phoenix + Inertia.js project, I had to install Node for the build stage to run npm install is the assets directory after it was copied. I wasn’t sure if I need to do something similar here. This is my first time using the Hex Bun package. I thought the Bun wrapper package eliminated the need to install any additional JavaScript tooling, but I might be mistaken.

  1. I was curious why you created the PhxManifestReader module. When I was trying to setup Vite within a Phoenix project, I noticed other examples had set manifest: false in their vite.config.ts and put the output to match the assets folder used by Phoenix. Is there an advantage to reading those manifest files in this manner?

Thanks again for creating this toolkit! Everything seems to be working in dev right now. This was also my first time using Igniter and the ExInertia installation experience was sweet.

2 Likes

Hi!

I forgot to bun install when running mix assets.deploy, but I’ve fixed this with the 0.7 release.

As for the PhxManifestReader question, we use PhxManifestReader to dynamically read Vite’s generated manifest file, allowing Phoenix to reference the correct, hashed asset filenames in production.

This approach leverages Vite’s chunking and cache-busting optimizations in both development and production, simplifying asset management without needing to disable the manifest in Vite.

Is there any expplanation on what Inertia is, and why everyone seems to be so excited about it?

I can’t get head or tails of it from the websites, and all integrations (exinertia included) have no docs whatsoever on how you would use it

1 Like

Inertia replaces your application’s view layer. Instead of using server-side rendering via PHP or Ruby templates, the views returned by your application are JavaScript page components. This allows you to build your entire frontend using React, Vue, or Svelte, while still enjoying the productivity of Laravel or your preferred server-side framework.
https://inertiajs.com/how-it-works

1 Like

I read that, too. And that means what exactly? I’ve always been able to build anything on the frontend using anything on the backend :slight_smile:

2 Likes

Hey! I updated and mix assets.deploy worked and the Docker image was able to be built. Thanks for making the changes!

After the update, everything continued to work fine in dev mode, but I ran into a few more issues with the deployed application. Here they are in the order I found them in the logs and the changes I made to fix them:

  1. (UndefinedFunctionError) function Mix.env/0 is undefined (module Mix is not available)

The function dev_env?/0 was being invoked in the *.html.heex files, which was causing the error. In the layouts module e.g. myapp_web/components/layouts.ex I added a module attribute so that the environment would be remembered at compile time:

- def dev_env? do
-    Mix.env() == :dev
- end  
  # Remember value at compile time since Mix is
  # not available at runtime
+ @env Mix.env()
+ def dev_env?, do: @env == :dev
  1. vite_manifest.json no such file or directory error

In my server logs, I was seeing

(File.Error) could not read file "priv/static/assets/vite_manifest.json": no such file or directory

I shelled into the Docker container to see if the assets were being generated. They were being generated so I checked out the PhxManifestReader module. I noticed that do_read/1 was using current_env/0. I think the issue was this line

      Application.get_env(:exinertia_demo, :env, Vite.default_env())

I think this was returning nil in production, but I’m not positive. Anyway, in config.exs I added config_env/0 so that the config file would know what environment mix release was run in

config :exinertia_demo,
  ecto_repos: [ExinertiaDemo.Repo],
+ env: config_env(),
  generators: [timestamp_type: :utc_datetime_usec, binary_id: true]
  1. 404 for *.js files

After the vite_manifest was read correctly, the webpages would finally load, but no *.js files could be found. As a result, pages rendered by Inertia.js were blank. I checked the source and logs and saw:

<script type="module" crossorigin="" defer="" phx-track-static="" src="/app.CR82hMY-.js">
  </script>
03:12:12.408 request_id=GCMIxx8X9K92UAIAAArB [info] GET /app.CR82hMY-.js
03:12:12.408 request_id=GCMIxx8X9K92UAIAAArB [info] Sent 404 in 189µs

CSS was working fine and I noticed /assets/ was missing on the src path. I’m not sure if it was supposed to be prepended after being read in PhxManifestReader, but I just updated the src for the layouts and prepended assets using string interpolation. I’m not sure if the other attributes on the script tag e.g. crossorigin, defer, are supposed to be something other than an empty string, but everything seems to be working right now.

- src={Vite.Manifest.main_js()}>
+ src={"/assets/#{Vite.Manifest.main_js()}"}

I have the code up on Github here and the demo app is running at https://exinertia-demo.skyturtle.io. I just added a default layout and a couple links to show prefetch.Thanks again for making this toolkit. I hope the the feedback helps. Please let me know if there is anything else I can clarify.

Also, any chance you have any examples of using the typed Routes module in a full code example or how to get SSR working for prod? If not, no worries. Thanks again!

Amazing feedback!

I’ve added all the necessary changes in order for everything to work flawlessly in the backend.

Changes were made to both the installer and the templates.

Would be great if you can check again just to make sure :innocent:

Cheers!

Hey! I ran the ExInertia Igniter setup on a fresh Phoenix v1.7.20 install. There was a couple small hiccups, but fairly easy fixes. It looks like everything you did with config_env() and def_env() worked out. However, I did see there was a change to the vite.config.ts in the React template, which was causing an error where the manifest file could not be found:

[error] Could not find static manifest at "/app/lib/ex_new_demo-0.1.0/priv/static/assets/vite_manifest.json". Run "mix phx.digest" after building your static files or remove the configuration from "config/prod.exs".

I deployed a demo app again with the changes below on a VPS and everything was working. I think mix igniter.install exinertia should work right away for dev and production if you incorporate the following changes:

  • Revert to the output directory and filenames you had previously in assets/vite.config.ts:

Reason: When /assets/ was not there, the vite_manifest.json could not be found. Additionally, since you are changing the outDir, prepending assets/ creates another subfolder which is not necessary, e.g. priv/static/assets/assets/inertia.hash.js.

- outDir: "../priv/static",
+ outDir: "../priv/static/assets/",
- entryFileNames: "assets/[name].[hash].js",
- chunkFileNames: "assets/[name].[hash].js",
- assetFileNames: "assets/[name].[hash][extname]",
+ entryFileNames: "[name].[hash].js",
+ chunkFileNames: "[name].[hash].js",
+ assetFileNames: "[name].[hash][extname]",
  • In the layout templates, use string interpolation to prepend /assets/ in the src attribute in the <script> tags for app.js and inertia.js

Reason: When the changes above for the vite.config.js are made, the assets are built correctly. Without having /assets/, the src attribute points to priv/static/ and *.js files are not found as that folder only has other digested static files like robots.txt, favicon.ico, etc. By having /assets/, the src attribute will point to priv/static/assets where all the *.js, *.tsx, etc file have been built and hashed by Vite.

root.html.heex:

- src={Vite.Manifest.main_js()}
+ src={"/assets/#{Vite.Manifest.main_js()}"}

inertia_root.html.heex:

- src={Vite.Manifest.inertia_js()}
+ src={"/assets/#{Vite.Manifest.inertia_js()}"}

Hope that helps!

2 Likes