An example project for a modern monolith using Phoenix, Inertia.js, React. You can find the application running at https://monolith.skyturtle.io/
It is deployed to a self-hosted VPS using Coolify. A healthcheck has been enabled using a custom plug module, MonolithWeb.Healthcheck.
On the home page you will see a basic navbar with three links: Home, Counter, and Default. Home and Counter are React components and can be found within the assets directory. The Home page receives props from a standard Phoenix controller. The navigation for the links between these pages uses Inertia with prefetch enabled. Default is the default home page that ships with mix phx.new my_app.
This project is built with Phoenix 1.7.19, Inertia v2, and React 19. Third-party packages are installed using npm so you will need to have Node.js installed.
To start your Phoenix server:
- Run
mix setupto install and setup dependencies - Start Phoenix endpoint with
mix phx.serveror inside IEx withiex -S mix phx.server
Now you can visit localhost:4000 from your browser. The home page is a React component being rendered by Inertia.
The official setup instructions can be found on HexDocs for the Inertia.js Phoenix Adapter. These setup instructions give a high-level overview of the steps. The details below are to help out my future self and also clariy some questions I had as I was going through the instructions.
I like trailing commas so I installed Freedom Formatter
- Add Freedom Formatter for trailing commas.
- Add the
:inertiapackage to your deps inmix.exsand runmix deps.get
# mix.exs
{:inertia, "~> 2.2.0"}- Add Inertia configuration to
confix.exs - Add Inertia helpers for Controller and HTML to
SvelixWeb - Add
Inertia.Plugto your browser pipeline in yourrouter.ex - Update
<head>component in the root layout so the client-side library will keep the title in sync
- Install the Inertia.js library for your frontend library as well as the frontend library itself. In this case, we will be using React. Remember, everything "frontend" related will be done in the
assetsdirectory. - Initialize the client-side library in
app.jsx. We renamedapp.jstoapp.jsxsince we are using React. - Update the
:esbuildconfig to bump up the version number. Runmix esbuild.installif the version number was changed. - Optional if you want code splitting and to use ESM: change the
esbuildto use:
# config.exs
~w(js/app.jsx --bundle --chunk-names=chunks/[name]-[hash] --splitting --format=esm --target=es2020 --outdir=../priv/static/assets --external fonts/* --external:/images/*),// `assets/tailwind.config.js`
module.exports = {
content: [
"./js/**/*.js",
"./js/**/*.jsx",
"./js/**/*.tsx",
"../lib/monolith_web.ex",
"../lib/monolith_web/**/*.*ex"
],In case you want TypeScript support
esbuild has a TypeScript loader that is enabled by default for .ts and .tsx files. It can parse files but will not do anything for type checking. There are some caveats so the esbuild docs recommend enabling isolatedModules and esModuleInterop in your tsconfig.json.
Add *.tsx files to the Tailwind config file.
This repo has an example route at /inertia. This page renders a React component, MessagePage, that is passed the props message and name. Things to note:
- We created a specific controller here (
ReactPageController), but you don't necessarily need to do that. You can render an Inertia response from an controller since we added theInertia.Controllerhelpers intoMonolithWeb. - As your frontend gets more complicated (e.g. more pages, components), you may want to break out more directories and use a
Layoutcomponent. The Inertia doc example show using aLayoutcomponent. You will need to(Update: )import Reactinto everyjsxcomponent. This is because of esbuild. You can auto-import React by adding the--jsx=automaticflag to the esbuild config inconfix.exs.
Update: Initially I tried using --jsx=automatic for the esbuild config so that I wouldn't have to import React into every file.
~w(js/app.jsx --bundle --chunk-names=chunks/[name]-[hash] --splitting --format=esm --target=es2020 --jsx=automatic --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),However, when I started setting up SSR, I was getting an error that React was not defined:
[error] ** (Inertia.SSR.RenderError) React is not defined
ReferenceError: React is not defined
at setup (/Users/leo/projects/monolith/priv/ssr.js:42041:54)
at /Users/leo/projects/monolith/priv/ssr.js:19759:22I removed the ---jsx=automatic flag because I was having too many issues trying to get SSR to work.
- Add the route to
router.ex
scope "/", MonolithWeb do
pipe_through :browser
get "/", PageController, :home
get "/inertia", ReactPageController, :index
end- Places the pages (React components) you want to render in
assets/js/pages/
// assets/js/pages/MessagePage.jsx
import React from "react";
import { Head } from '@inertiajs/react'
export default function MessagePage({ message, name }) {
return (
<div>
<Head title="Welcome" />
<h1 class="text-2xl font-bold underline">{message}</h1>
<p class="pt-4">Hello <span class="text-green-600 font-semibold">{name}</span>, welcome to your first Inertia app!</p>
</div>
)
}- In your controller, render the response using
render_inertia/1
def index(conn, _params) do
conn
|> assign_prop(:message, "Hello from Inertia!")
|> assign_prop(:name, "Turtle")
|> render_inertia("MessagePage")
end- create Dockerfile using mix release
- need to add Node.js to BUILDER and RUNNER image so that SSR works
Need it in the build stage since we installed third-party NPM packages
- in Coolify, create a new Project and select Docker file
- change Ports Exposes to 4000
- set environment variables DATABASE_URL, PHX_HOST, SECRET_KEY_BASE, POSTMARK_API_KEY (for when you set up email)
- dont forget to add new file extensions to Tailwind config
- add
npm installto mix aliases even though youre usign esbuild