Home

Awesome

Inertia.js with Phoenix and Vue 3 Setup

This project contains a sample project and guide that walks you through setting up a new Phoenix project with Inertia.js and Vue 3, using SQLite3 for the database, without LiveView and esbuild with SSR.

You can view this application at: https://inertia-vue.fly.dev/

Create a New Phoenix Project

mix phx.new inertia_vue --database sqlite3 --no-live --no-esbuild

Follow the Inertia Phoenix README

Follow the instructions in the Inertia Phoenix README to set up the base project (https://github.com/inertiajs/inertia-phoenix). Once done, make the following changes:

Install Dependencies

Change directory to the assets folder and install the necessary dependencies:

cd assets
npm i -D esbuild esbuild-plugin-vue3
npm i @inertiajs/vue3 ../deps/phoenix ../deps/phoenix_html ../deps/phoenix_live_view @vue/server-renderer

Create assets/js/app.js

import "phoenix_html";
import { createSSRApp, h } from "vue";
import { createInertiaApp } from "@inertiajs/vue3";
import axios from "axios";
axios.defaults.xsrfHeaderName = "x-csrf-token";

createInertiaApp({
  resolve: async (name) => {
    const page = await import(`./Pages/${name}.vue`);
    return page;
  },
  setup({ el, App, props, plugin }) {
    createSSRApp({ render: () => h(App, props) })
      .use(plugin)
      .mount(el);
  },
});

Create assets/js/ssr.js

import { createInertiaApp } from "@inertiajs/vue3";
import { renderToString } from "@vue/server-renderer";
import { createSSRApp, h } from "vue";

export function render(page) {
  return createInertiaApp({
    page,
    render: renderToString,
    resolve: async (name) => {
      const page = await import(`./Pages/${name}.vue`);
      return page;
    },
    setup({ App, props, plugin }) {
      return createSSRApp({
        render: () => h(App, props),
      }).use(plugin);
    },
  });
}

Create assets/build.js

const esbuild = require("esbuild");
const vuePlugin = require("esbuild-plugin-vue3");

const args = process.argv.slice(2);
const watch = args.includes("--watch");
const deploy = args.includes("--deploy");

const loader = {
  // Add loaders for images/fonts/etc, e.g. { '.svg': 'file' }
};

const plugins = [
  // Add and configure plugins here
  vuePlugin(),
];

// Define esbuild options
let opts = {
  entryPoints: ["js/app.js"],
  bundle: true,
  logLevel: "info",
  target: "es2020",
  outdir: "../priv/static/assets",
  external: ["*.css", "fonts/*", "images/*"],
  nodePaths: ["../deps"],
  loader: loader,
  plugins: plugins,
};

if (deploy) {
  opts = {
    ...opts,
    minify: true,
  };
}

if (watch) {
  opts = {
    ...opts,
    sourcemap: "inline",
  };
  esbuild
    .context(opts)
    .then((ctx) => {
      ctx.watch();
    })
    .catch((_error) => {
      process.exit(1);
    });
} else {
  esbuild.build(opts);
}

Create assets/build-ssr.js

const esbuild = require("esbuild");
const vuePlugin = require("esbuild-plugin-vue3");

const args = process.argv.slice(2);
const watch = args.includes("--watch");
const deploy = args.includes("--deploy");

const loader = {
  // Add loaders for images/fonts/etc, e.g. { '.svg': 'file' }
};

const plugins = [
  // Add and configure plugins here
  vuePlugin(),
];

// Define esbuild options
let opts = {
  entryPoints: ["js/ssr.js"],
  bundle: true,
  logLevel: "info",
  platform: "node",
  format: "cjs",
  outdir: "../priv",
  nodePaths: ["../deps"],
  loader: loader,
  plugins: plugins,
};

if (deploy) {
  opts = {
    ...opts,
    minify: true,
  };
}

if (watch) {
  opts = {
    ...opts,
    sourcemap: "inline",
  };
  esbuild
    .context(opts)
    .then((ctx) => {
      ctx.watch();
    })
    .catch((_error) => {
      process.exit(1);
    });
} else {
  esbuild.build(opts);
}

Adjust tailwind.config.js

Add "./js/**/*.vue", to the content array in tailwind.config.js:

module.exports = {
  content: [
    "./js/**/*.vue",
    // other paths
  ],
  // other config
};

Update config/config.exs

config :inertia,
  endpoint: InertiaVueWeb.Endpoint,
  static_paths: ["/assets/app.js"],
  default_version: "1",
  ssr: true,
  raise_on_ssr_failure: true

Update application.ex

Add this to your registry in the start function in your application.ex file:

{Inertia.SSR, path: Path.join([Application.app_dir(:inertia_vue), "priv"])}

Update config/dev.exs

Add the following to the watchers under config/dev.exs:

node: ["build.js", "--watch", cd: Path.expand("../assets", __DIR__)]
node: ["build-ssr.js", "--watch", cd: Path.expand("../assets", __DIR__)]

Update mix.exs

Adjust assets.setup under aliases in mix.exs:

"assets.setup": ["tailwind.install --if-missing", "cmd --cd assets npm install"]

Adjust assets.deploy under aliases in mix.exs:

"assets.deploy": [
  "tailwind default --minify",
  "cmd --cd assets node build.js --deploy",
  "cmd --cd assets node build-ssr.js --deploy",
  "phx.digest"
]

Create Home.vue

Create Home.vue under assets/js/Pages/Home.vue:

<script setup lang="ts">
import { Head } from "@inertiajs/vue3";

defineProps<{
  name: string;
}>();
</script>

<template>
  <Head title="Welcome to Inertia" />
  <h1 class="text-4xl">Welcome</h1>
  <p>Hello {{ name }}, welcome to your first Inertia app!</p>
</template>

Clean Up Controllers (if you only plan on rendering inertia pages)

Delete the pages_html directory and page_html.ex file under the controllers directory in your lib web folder.

Update page_controller.ex

def home(conn, _params) do
  conn
  |> assign_prop(:name, "My Name (from Server)")
  |> render_inertia("Home")
end

This documentation provides a clear and structured guide for setting up Inertia.js with Phoenix and Vue 3, including the necessary steps and configurations.

Disclaimer

If I have missed anything, please feel free to reach out or create an issue.