phoenix_inertia_svelte_template Svelte Themes

Phoenix_inertia_svelte_template

A starter template for Phoenix 1.8+ with Inertia.js and Svelte 5 using esbuild

Phoenix + Inertia.js + Svelte 5 Template

A starter template for building modern web applications with Phoenix 1.8+, Inertia.js, and Svelte 5 using esbuild (not Vite).

Why This Template?

Setting up Inertia.js with Svelte 5 and Phoenix can be tricky. The main gotcha is that Inertia expects components in ES module format ({ default: Component }), but esbuild's static imports return components directly. This template has the fix pre-configured so you can get started immediately.

Features

  • Phoenix 1.8+ with Bandit adapter
  • Inertia.js 2.x for seamless SPA-like navigation
  • Svelte 5 with legacy export let syntax for best Inertia compatibility
  • esbuild for fast JavaScript bundling
  • Tailwind CSS 3.4 for styling
  • Pre-configured development workflow with hot reloading

Prerequisites

Before you begin, ensure you have the following installed:

Elixir & Erlang

macOS (using Homebrew):

brew install elixir

Ubuntu/Debian:

wget https://packages.erlang-solutions.com/erlang-solutions_2.0_all.deb
sudo dpkg -i erlang-solutions_2.0_all.deb
sudo apt-get update
sudo apt-get install esl-erlang elixir

Using asdf (recommended for version management):

asdf plugin add erlang
asdf plugin add elixir
asdf install erlang 26.2
asdf install elixir 1.16.0-otp-26
asdf global erlang 26.2
asdf global elixir 1.16.0-otp-26

Verify installation:

elixir --version
# Erlang/OTP 26 [erts-14.x]
# Elixir 1.16.x

Node.js & npm

macOS (using Homebrew):

brew install node

Using nvm (recommended):

nvm install 20
nvm use 20

Verify installation:

node --version  # v18.x or higher
npm --version   # 9.x or higher

Hex and Phoenix

After installing Elixir, install Hex (Elixir's package manager) and the Phoenix application generator:

mix local.hex --force
mix archive.install hex phx_new --force

Quick Start

1. Clone the repository

git clone https://github.com/yourusername/phoenix_inertia_svelte_template.git my_app
cd my_app

Replace all occurrences of the app name:

# On macOS/Linux, you can use sed:
# Replace "phoenix_inertia_svelte" with "my_app" (snake_case)
# Replace "PhoenixInertiaSvelte" with "MyApp" (PascalCase)

find . -type f -name "*.ex" -o -name "*.exs" -o -name "*.heex" -o -name "*.js" -o -name "*.json" | \
  xargs sed -i '' 's/phoenix_inertia_svelte/my_app/g; s/PhoenixInertiaSvelte/MyApp/g'

# Also rename the lib directories
mv lib/phoenix_inertia_svelte lib/my_app
mv lib/phoenix_inertia_svelte_web lib/my_app_web
mv lib/phoenix_inertia_svelte_web.ex lib/my_app_web.ex

3. Install dependencies

Run the setup command which installs both Elixir and Node.js dependencies:

mix setup

This runs:

  • mix deps.get - Fetches Elixir dependencies
  • mix assets.setup - Installs Tailwind and Node.js dependencies
  • mix assets.build - Builds CSS and JavaScript assets

4. Start the development server

mix phx.server

Or run inside IEx (Interactive Elixir) for debugging:

iex -S mix phx.server

5. Visit the app

Open http://localhost:4000 in your browser.


Manual Setup (Step by Step)

If you prefer to install dependencies manually or mix setup fails:

Install Elixir dependencies

mix deps.get

Install Node.js dependencies

cd assets
npm install
cd ..

Install Tailwind standalone CLI

mix tailwind.install

Build assets

# Build Tailwind CSS
mix tailwind phoenix_inertia_svelte

# Build JavaScript with esbuild
cd assets && node build.js && cd ..

Start the server

mix phx.server

Dependencies Overview

Elixir Dependencies (mix.exs)

Package Version Purpose
phoenix ~> 1.8.0 Web framework
phoenix_html ~> 4.2 HTML helpers
phoenix_live_view ~> 1.0 Real-time features (optional)
phoenix_live_reload ~> 1.2 Development hot reload
inertia ~> 2.5 Inertia.js Phoenix adapter
bandit ~> 1.6 HTTP server
tailwind ~> 0.3 Tailwind CSS integration
esbuild ~> 0.9 JavaScript bundling (for Tailwind)
jason ~> 1.2 JSON encoding/decoding
dns_cluster ~> 0.1.1 DNS-based clustering
telemetry_metrics ~> 1.0 Metrics
telemetry_poller ~> 1.0 Telemetry polling

Node.js Dependencies (assets/package.json)

Package Version Purpose
@inertiajs/svelte ^2.0.0 Inertia.js Svelte adapter
svelte ^5.0.0 Svelte framework
esbuild ^0.24.0 JavaScript bundler
esbuild-svelte ^0.9.0 Svelte plugin for esbuild
svelte-preprocess ^6.0.0 Svelte preprocessor

Project Structure

├── assets/
│   ├── css/
│   │   └── app.css                 # Tailwind CSS entry point
│   ├── js/
│   │   ├── app.js                  # Inertia.js setup & page registry
│   │   └── pages/
│   │       └── Home.svelte         # Sample Svelte page component
│   ├── build.js                    # esbuild configuration
│   ├── package.json                # Node.js dependencies
│   └── tailwind.config.js          # Tailwind configuration
├── config/
│   ├── config.exs                  # Base configuration (Inertia config here)
│   ├── dev.exs                     # Development settings
│   ├── prod.exs                    # Production settings
│   ├── runtime.exs                 # Runtime configuration
│   └── test.exs                    # Test settings
├── lib/
│   ├── phoenix_inertia_svelte/
│   │   └── application.ex          # Application supervisor
│   └── phoenix_inertia_svelte_web/
│       ├── components/
│       │   ├── layouts.ex          # Layout module
│       │   └── layouts/
│       │       ├── root.html.heex  # Root HTML template
│       │       └── app.html.heex   # App layout
│       ├── controllers/
│       │   ├── error_html.ex       # HTML error handling
│       │   ├── error_json.ex       # JSON error handling
│       │   └── page_controller.ex  # Sample controller
│       ├── endpoint.ex             # Phoenix endpoint
│       ├── router.ex               # Routes definition
│       └── telemetry.ex            # Telemetry setup
├── priv/
│   └── static/                     # Static assets (generated)
├── test/                           # Test files
├── mix.exs                         # Elixir project definition
├── .formatter.exs                  # Elixir formatter config
└── .gitignore

How It Works

The Request Flow

  1. Browser requests a page (e.g., /)
  2. Phoenix Router (router.ex) matches the route and calls the controller
  3. Controller (page_controller.ex) prepares props and calls render_inertia("PageName")
  4. Inertia Plug renders the root layout with a <div id="app" data-page="..."> containing JSON props
  5. JavaScript (app.js) picks up the data-page attribute and mounts the Svelte component
  6. Svelte Component renders with the props from Phoenix

Subsequent Navigation

After the initial page load, Inertia intercepts link clicks and:

  1. Makes an XHR request to the new URL
  2. Phoenix returns only JSON (props + component name)
  3. Inertia swaps the component without a full page reload

Adding New Pages

1. Create the Svelte component

<!-- assets/js/pages/About.svelte -->
<script>
  export let title = "About Us";
  export let description = "";
</script>

<svelte:head>
  <title>{title}</title>
</svelte:head>

<main class="container mx-auto px-4 py-8">
  <h1 class="text-3xl font-bold">{title}</h1>
  <p class="mt-4 text-gray-600">{description}</p>
</main>

2. Register the component in app.js

// assets/js/app.js
import Home from "./pages/Home.svelte";
import About from "./pages/About.svelte";

const pages = {
  Home: { default: Home },
  About: { default: About },  // Add this line
};

3. Create the controller action

# lib/phoenix_inertia_svelte_web/controllers/page_controller.ex
def about(conn, _params) do
  conn
  |> assign_prop(:title, "About Our Company")
  |> assign_prop(:description, "We build amazing things.")
  |> render_inertia("About")
end

4. Add the route

# lib/phoenix_inertia_svelte_web/router.ex
scope "/", PhoenixInertiaSvelteWeb do
  pipe_through :browser

  get "/", PageController, :home
  get "/about", PageController, :about  # Add this line
end

Important: The Module Wrapper Fix

This is the key configuration that makes everything work. When using esbuild (not Vite), you must wrap component imports in the ES module format Inertia expects:

// assets/js/app.js

// Import components directly
import Home from "./pages/Home.svelte";

// Wrap in module format { default: Component }
const pages = {
  Home: { default: Home },      // Correct - Inertia expects this format
  // Home: Home,                // WRONG - will cause blank page!
};

Why? Inertia.js expects import() or import.meta.glob() which return { default: Component }. With esbuild static imports, you get the component directly, so we manually wrap it.


Svelte 5 Compatibility

This template uses Svelte 5 with the legacy export let syntax for props. This ensures maximum compatibility with Inertia.js:

<!-- Recommended: Legacy style (best compatibility) -->
<script>
  export let title;
  export let users = [];
</script>

<!-- Also works but may have edge cases -->
<script>
  let { title, users = [] } = $props();
</script>

Configuration

Inertia Configuration

Located in config/config.exs:

config :inertia,
  endpoint: PhoenixInertiaSvelteWeb.Endpoint,
  static_paths: ["/assets/app.js", "/assets/app.css"],
  default_version: "1",
  camelize_props: true,   # snake_case -> camelCase
  ssr: false,             # Server-side rendering (disabled)
  raise_on_ssr_failure: false

Tailwind Configuration

Located in assets/tailwind.config.js. The content paths include Svelte files:

content: [
  "./js/**/*.js",
  "./js/**/*.svelte",  // Include Svelte components
  "../lib/phoenix_inertia_svelte_web.ex",
  "../lib/phoenix_inertia_svelte_web/**/*.*ex",
],

Development

Running the server

mix phx.server

Running tests

mix test

Formatting code

mix format

Building assets manually

# Tailwind CSS
mix tailwind phoenix_inertia_svelte

# JavaScript (with watch mode)
cd assets && node build.js --watch

Production Deployment

1. Build assets for production

mix assets.deploy

This minifies CSS and JavaScript and generates digested filenames.

2. Generate a secret key

mix phx.gen.secret

3. Run with environment variables

SECRET_KEY_BASE=your_64_char_secret \
PHX_HOST=example.com \
PORT=4000 \
PHX_SERVER=true \
MIX_ENV=prod \
mix phx.server
MIX_ENV=prod mix release
_build/prod/rel/phoenix_inertia_svelte/bin/phoenix_inertia_svelte start

Troubleshooting

Blank page with no console errors

Cause: The module wrapper issue - component isn't wrapped in { default: Component } format.

Solution: Ensure all pages in assets/js/app.js use the correct format:

const pages = {
  Home: { default: Home },  // Correct
};

"Page not found: PageName" error

Cause: The page name from Phoenix doesn't match the key in your pages object.

Solution: Names are case-sensitive. If your controller calls render_inertia("About"), your pages object needs About: { default: About }.

Props not received in Svelte component

Cause: Prop naming mismatch due to camelize_props setting.

Solution: Check config/config.exs. If camelize_props: true:

  • Phoenix: assign_prop(:user_name, "John")
  • Svelte: export let userName;

Assets not updating

Solution:

# Clear and rebuild
rm -rf priv/static/assets
mix assets.build

"esbuild-svelte" or "svelte" not found

Solution:

cd assets
rm -rf node_modules
npm install

Version Compatibility

Package Version Notes
Elixir 1.14+ Required
Erlang/OTP 25+ Required
Node.js 18+ Required
Phoenix 1.8.x Latest stable
@inertiajs/svelte 2.x Required for Svelte 5
svelte 5.x Works with legacy and runes mode
esbuild-svelte 0.9.x Svelte 5 compatible
inertia (Elixir) 2.x Phoenix adapter

Resources


License

MIT

Top categories

Loading Svelte Themes