{"id":13513815,"url":"https://github.com/inertiajs/inertia-phoenix","last_synced_at":"2025-05-15T04:08:19.124Z","repository":{"id":176532535,"uuid":"658439072","full_name":"inertiajs/inertia-phoenix","owner":"inertiajs","description":"The Phoenix adapter for Inertia.js.","archived":false,"fork":false,"pushed_at":"2025-04-25T13:28:55.000Z","size":166,"stargazers_count":354,"open_issues_count":4,"forks_count":18,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-04-25T14:26:39.329Z","etag":null,"topics":["elixir","inertiajs"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/inertia","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/inertiajs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-06-25T18:38:17.000Z","updated_at":"2025-04-25T13:28:59.000Z","dependencies_parsed_at":"2024-05-01T21:00:16.594Z","dependency_job_id":"bd72a5b4-0c9a-4a80-ba4b-7b92d1722b66","html_url":"https://github.com/inertiajs/inertia-phoenix","commit_stats":{"total_commits":130,"total_committers":8,"mean_commits":16.25,"dds":0.0692307692307692,"last_synced_commit":"0b1f178e44471b7d4dc6a6ec9c6119b23d685354"},"previous_names":["svycal/inertia-phoenix","inertiajs/inertia-phoenix"],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inertiajs%2Finertia-phoenix","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inertiajs%2Finertia-phoenix/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inertiajs%2Finertia-phoenix/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inertiajs%2Finertia-phoenix/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/inertiajs","download_url":"https://codeload.github.com/inertiajs/inertia-phoenix/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254270656,"owners_count":22042860,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","inertiajs"],"created_at":"2024-08-01T05:00:38.186Z","updated_at":"2025-05-15T04:08:14.103Z","avatar_url":"https://github.com/inertiajs.png","language":"Elixir","funding_links":[],"categories":["Elixir","Adapters"],"sub_categories":["Server-side"],"readme":"# Inertia.js Phoenix Adapter [![Hex Package](https://img.shields.io/hexpm/v/inertia)](https://hex.pm/packages/inertia) [![Hex Docs](https://img.shields.io/badge/docs-green)](https://hexdocs.pm/inertia/readme.html)\n\nThe official Elixir/Phoenix adapter for [Inertia.js](https://inertiajs.com/).\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Rendering responses](#rendering-responses)\n- [Setting up the client-side](#setting-up-the-client-side)\n- [Lazy data evaluation](#lazy-data-evaluation)\n- [Deferred props](#deferred-props)\n- [Merge props](#merge-props)\n- [Shared data](#shared-data)\n- [Validations](#validations)\n- [Flash messages](#flash-messages)\n- [CSRF protection](#csrf-protection)\n- [History](#history)\n- [Testing](#testing)\n- [Server-side rendering](#server-side-rendering)\n\n## Installation\n\nThe package can be installed by adding `inertia` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:inertia, \"~\u003e 2.3.0\"}\n  ]\nend\n```\n\nAdd your desired configuration in your `config.exs` file:\n\n```elixir\n# config/config.exs\n\nconfig :inertia,\n  # The Phoenix Endpoint module for your application. This is used for building\n  # asset URLs to compute a unique version hash to track when something has\n  # changed (and a reload is required on the frontend).\n  endpoint: MyAppWeb.Endpoint,\n\n  # An optional list of static file paths to track for changes. You'll generally\n  # want to include any JavaScript assets that may require a page refresh when\n  # modified.\n  static_paths: [\"/assets/app.js\"],\n\n  # The default version string to use (if you decide not to track any static\n  # assets using the `static_paths` config). Defaults to \"1\".\n  default_version: \"1\",\n\n  # Enable automatic conversion of prop keys from snake case (e.g. `inserted_at`),\n  # which is conventional in Elixir, to camel case (e.g. `insertedAt`), which is\n  # conventional in JavaScript. Defaults to `false`.\n  camelize_props: false,\n\n  # Instruct the client side whether to encrypt the page object in the window history\n  # state. This can also be set/overridden on a per-request basis, using the `encrypt_history`\n  # controller helper. Defaults to `false`.\n  history: [encrypt: false],\n\n  # Enable server-side rendering for page responses (requires some additional setup,\n  # see instructions below). Defaults to `false`.\n  ssr: false,\n\n  # Whether to raise an exception when server-side rendering fails (only applies\n  # when SSR is enabled). Defaults to `true`.\n  #\n  # Recommended: enable in non-production environments and disable in production,\n  # so that SSR failures will not cause 500 errors (but instead will fallback to\n  # CSR).\n  raise_on_ssr_failure: config_env() != :prod\n```\n\nThis library includes a few modules to help render Inertia responses:\n\n- [`Inertia.Plug`](https://hexdocs.pm/inertia/Inertia.Plug.html): a plug for detecting Inertia.js requests and preparing the connection accordingly.\n- [`Inertia.Controller`](https://hexdocs.pm/inertia/Inertia.Controller.html): controller functions for rendering Inertia.js-compatible responses.\n- [`Inertia.HTML`](https://hexdocs.pm/inertia/Inertia.HTML.html): HTML components for Inertia-powered views.\n\nTo get started, import `Inertia.Controller` in your controller helper and `Inertia.HTML` in your html helper:\n\n```diff\n  # lib/my_app_web.ex\n  defmodule MyAppWeb do\n    def controller do\n      quote do\n        use Phoenix.Controller, namespace: MyAppWeb\n\n+       import Inertia.Controller\n      end\n    end\n\n    def html do\n      quote do\n        use Phoenix.Component\n\n+       import Inertia.HTML\n      end\n    end\n  end\n```\n\nThen, install the plug in your browser pipeline:\n\n```diff\n  # lib/my_app_web/router.ex\n  defmodule MyAppWeb.Router do\n    use MyAppWeb, :router\n\n    pipeline :browser do\n      plug :accepts, [\"html\"]\n\n+     plug Inertia.Plug\n    end\n  end\n```\n\nNext, replace the title tag in your layout with the `\u003c.inertia_title\u003e` component, so that the client-side library will keep the title in sync, and add the `\u003c.inertia_head\u003e` component:\n\n```diff\n  # lib/my_app_web/components/layouts/root.html.heex\n  \u003c!DOCTYPE html\u003e\n  \u003chtml lang=\"en\" class=\"[scrollbar-gutter:stable]\"\u003e\n    \u003chead\u003e\n-     \u003c.live_title\u003e{@page_title}\u003c/.live_title\u003e\n+     \u003c.inertia_title\u003e{@page_title}\u003c/.inertia_title\u003e\n+     \u003c.inertia_head content={@inertia_head} /\u003e\n    \u003c/head\u003e\n```\n\nYou're now ready to start rendering inertia responses!\n\n## Rendering responses\n\nRendering an Inertia.js response looks like this:\n\n```elixir\ndefmodule MyAppWeb.ProfileController do\n  use MyAppWeb, :controller\n\n  def index(conn, _params) do\n    conn\n    |\u003e assign_prop(:text, \"Hello world\")\n    |\u003e render_inertia(\"ProfilePage\")\n  end\nend\n```\n\nThe `assign_prop` function allows you to define props that should be passed in to the component. The `render_inertia` function accepts the conn, the name of the component to render, and an optional map containing more initial props to pass to the page component.\n\nThis action will render an HTML page containing a `\u003cdiv\u003e` element with the name of the component and the initial props, following Inertia.js conventions. On subsequent requests dispatched by the Inertia.js client library, this action will return a JSON response with the data necessary for rendering the page.\n\nIf you want to automatically convert your prop keys from snake case (conventional in Elixir) to camel case to keep with JavaScript conventions (e.g. `first_name` to `firstName`), you can configure that globally or enable/disable it on a per-request basis.\n\n```elixir\nimport Config\n\nconfig :inertia,\n  endpoint: MyAppWeb.Endpoint,\n  camelize_props: true\n```\n\n```elixir\ndefmodule MyAppWeb.ProfileController do\n  use MyAppWeb, :controller\n\n  def index(conn, _params) do\n    conn\n    |\u003e assign_prop(:first_name, \"Bob\")\n    |\u003e camelize_props()\n    |\u003e render_inertia(\"ProfilePage\")\n  end\nend\n```\n\n## Setting up the client-side\n\nThe [Inertia.js docs](https://inertiajs.com/client-side-setup) provide a good general walk-through on how to setup your JavaScript assets to boot your Inertia app. If you're new to Inertia, we recommend checking that out to familiarize yourself with how it all works. Here we'll provide some guidance on getting your Phoenix app with esbuild configured for basic client-side rendering (and further down, we'll delve into server-side rendering).\n\nTo get started, install the Inertia.js library for the frontend framework of your choice. In these instructions we'll use React, but the process is similar for other Inertia-compatible frameworks, like Vue or Svelte.\n\n```\ncd assets\nnpm install @inertiajs/react react react-dom\n```\n\nReplace the contents of your `app.js` file with the Inertia boot function and rename it to `app.jsx` (since we are using JSX).\n\n```javascript\n// assets/js/app.jsx\n\nimport React from \"react\";\nimport axios from \"axios\";\n\nimport { createInertiaApp } from \"@inertiajs/react\";\nimport { createRoot } from \"react-dom/client\";\n\naxios.defaults.xsrfHeaderName = \"x-csrf-token\";\n\ncreateInertiaApp({\n  resolve: async (name) =\u003e {\n    return await import(`./pages/${name}.jsx`);\n  },\n  setup({ App, el, props }) {\n    createRoot(el).render(\u003cApp {...props} /\u003e);\n  },\n});\n```\n\nThe example above assumes your pages live in the `assets/js/pages` directory and have a default export with page component, like this:\n\n```javascript\n// assets/js/pages/Dashboard.jsx\n\nimport React from \"react\";\n\nconst Dashboard = () =\u003e {\n  return (\n    \u003cdiv\u003e\n      {/* ... page contents ...*/}\n    \u003c/div\u003e\n  );\n}\n\nexport default Dashboard;\n```\n\nNext, make some adjustments to your esbuild config:\n\n- Ensure the version is \u003e= 0.19.0 (this is required for glob-style imports for your pages)\n- Update your entrypoint filename to the correct `.jsx` extension\n- Ensure your build `--target` is at least `es2020`\n\n```elixir\n# config/config.exs\n\nconfig :esbuild,\n  version: \"0.21.5\",\n  my_app: [\n    args: ~w(js/app.jsx --bundle --target=es2020 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),\n    cd: Path.expand(\"../assets\", __DIR__),\n    env: %{\"NODE_PATH\" =\u003e Path.expand(\"../deps\", __DIR__)}\n  ]\n```\n\nIf you updated your esbuild version, you'll need to run `mix esbuild.install` to fetch the new version.\n\nEsbuild also supports code-splitting, which can be useful for larger applications. To enable it, you'll need to:\n\n- Set the `format` as [`esm`](https://esbuild.github.io/api/#format-esm)\n- Add the [`--splitting`](https://esbuild.github.io/api/#splitting) flag\n- Optionally, set the [`chunk-names`](https://esbuild.github.io/api/#chunk-names) flag to customize the output filenames\n\n```diff\n  # config/config.exs\n\n  config :esbuild,\n    version: \"0.21.5\",\n    my_app: [\n-     args: ~w(js/app.jsx --bundle --target=es2020 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),\n+     args: ~w(js/app.jsx --bundle --chunk-names=chunks/[name]-[hash] --splitting --format=esm --target=es2020 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),\n      cd: Path.expand(\"../assets\", __DIR__),\n      env: %{\"NODE_PATH\" =\u003e Path.expand(\"../deps\", __DIR__)}\n    ]\n```\n\nAfter that, we need to update our root layout to load the JavaScript bundle as an ESM module (by changing the `type` attribute from `text/javascript` to `module`):\n\n```diff\n  # lib/my_app_web/components/layouts/root.html.eex\n\n-  \u003cscript type='text/javascript' defer phx-track-static src={~p\"/assets/app.js\"}\u003e\u003c/script\u003e\n+  \u003cscript type='module' defer phx-track-static src={~p\"/assets/app.js\"}\u003e\u003c/script\u003e\n```\n\n\u003e [!NOTE]\n\u003e ESM code splitting requires modern browser support.\n\u003e While most current browsers support ESM modules, you should verify compatibility requirements with your target audience.\n\u003e You can read more about how code-splitting works with esbuild in the [official documentation](https://esbuild.github.io/api/#chunk-names).\n\n## Lazy data evaluation\n\nIf you have expensive data for your props that may not always be required (that is, if you plan to use [partial reloads](https://inertiajs.com/partial-reloads)), you can wrap your expensive computation in a function and pass the function reference when setting your Inertia props. You may use either an anonymous function (or named function reference) and optionally wrap it with the `Inertia.Controller.inertia_optional/1` function.\n\n\u003e [!NOTE]\n\u003e `inertia_optional` props will _only_ be included the when explicitly requested in a partial\n\u003e reload. If you want to include the prop on first visit, you'll want to use a\n\u003e bare anonymous function or named function reference instead. See below for\n\u003e examples of how prop assignment behaves.\n\nHere are some specific examples of how the methods of lazy data evaluation differ:\n\n```elixir\nconn\n# ALWAYS included on first visit...\n# OPTIONALLY included on partial reloads...\n# ALWAYS evaluated...\n|\u003e assign_prop(:cheap_thing, cheap_thing())\n\n# ALWAYS included on first visit...\n# OPTIONALLY included on partial reloads...\n# ONLY evaluated when needed...\n|\u003e assign_prop(:expensive_thing, fn -\u003e calculate_thing() end)\n|\u003e assign_prop(:another_expensive_thing, \u0026calculate_another_thing/0)\n\n# NEVER included on first visit...\n# OPTIONALLY included on partial reloads...\n# ONLY evaluated when needed...\n|\u003e assign_prop(:super_expensive_thing, inertia_optional(fn -\u003e calculate_thing() end))\n```\n\n## Deferred props\n\n**Requires Inertia v2.x on the client-side**.\n\nIf you have expensive data that you'd like to automatically fetch (from the client-side via an async background request) after the page is initially rendered, you can mark the prop as deferred:\n\n```elixir\nconn\n|\u003e assign_prop(:expensive_thing, inertia_defer(fn -\u003e calculate_thing() end))\n```\n\nThe `inertia_defer/1` helper accepts a function argument in the first position. You may optionally use the `inertia_defer/2` helper, which accepts a \"group\" name in the second position:\n\n```elixir\nconn\n|\u003e assign_prop(:expensive_thing, inertia_defer(fn -\u003e calculate_thing() end, \"dashboard\"))\n```\n\nIf no group names are specified, then the client-side will issue a single async request to fetch all the deferred props. If there are multiple group names, then the client-side will issue one async request per group instead. This is useful if you have some very expensive data that you'd prefer fetch in parallel alongside other expensive data.\n\n## Merge props\n\n**Requires Inertia v2.x on the client-side**.\n\nIf you have prop data that should get merged with the existing data on the client-side on subsequent requests (for example, an array of paginated data being presented in an \"infinite scroll\" interface), then you can tag the prop value using the `inertia_merge/1` helper:\n\n```elixir\nconn\n|\u003e assign_prop(:paginated_list, inertia_merge([\"a\", \"b\", \"c\"]))\n```\n\nMerge props can also accept deferred props:\n\n```elixir\nconn\n|\u003e assign_prop(:paginated_list, inertia_defer(\u0026calculate_next_page/0) |\u003e inertia_merge())\n```\n\n## Shared data\n\nTo share data on every request, you can use the `assign_prop/2` function inside of a shared plug in your response pipeline. For example, suppose you have a `UserAuth` plug responsible for fetching the currently-logged in user and you want to be sure all your Inertia components receive that user data. Your plug might look something like this:\n\n```elixir\ndefmodule MyApp.UserAuth do\n  import Inertia.Controller\n  import Phoenix.Controller\n  import Plug.Conn\n\n  def authenticate_user(conn, _opts) do\n    user = get_user_from_session(conn)\n\n    # Here we are storing the user in the conn assigns (so\n    # we can use it for things like checking permissions later on),\n    # AND we are assigning a serialized represention of the user\n    # to our Inertia props.\n    conn\n    |\u003e assign(:user, user)\n    |\u003e assign_prop(:user, serialize_user(user))\n  end\n\n  # ...\nend\n```\n\nAnywhere this plug is used, the serialized `user` prop will be passed to the Inertia component.\n\n## Validations\n\nValidation errors follow some specific conventions to make wiring up with Inertia's form helpers seamless. The `errors` prop is managed by this library and is always included in the props object for Inertia components. (When there are no errors, the `errors` prop will be an empty object).\n\nThe `assign_errors` function is how you tell Inertia what errors should be represented on the front-end. By default, you can either pass an `Ecto.Changeset` struct or a bare map to the `assign_errors` function. For other error data types, you may implement the `Inertia.Errors` protocol (see the `Inertia.Errors` module docs for more information).\n\n```elixir\ndef update(conn, params) do\n  case MyApp.Settings.update(params) do\n    {:ok, _settings} -\u003e\n      conn\n      |\u003e put_flash(:info, \"Settings updated\")\n      |\u003e redirect(to: ~p\"/settings\")\n\n    {:error, changeset} -\u003e\n      conn\n      |\u003e assign_errors(changeset)\n      |\u003e redirect(to: ~p\"/settings\")\n  end\nend\n```\n\nThe `assign_errors` function will automatically convert the changeset errors into a shape compatible with the client-side adapter. Since Inertia.js expects a flat map of key-value pairs, the error serializer will flatten nested errors down to compound keys:\n\n```javascript\n{\n  \"name\" =\u003e \"can't be blank\",\n\n  // Nested errors keys are flattened with a dot separator (`.`)\n  \"team.name\" =\u003e \"must be at least 3 characters long\",\n\n  // Nested arrays are zero-based and indexed using bracket notation (`[0]`)\n  \"items[1].price\" =\u003e \"must be greater than 0\"\n}\n```\n\nErrors are automatically preserved across redirects, so you can safely respond with a redirect back to page where the form lives to display form errors.\n\nIf you need to construct your own map of errors (rather than pass in a changeset), be sure it's a flat mapping of atom (or string) keys to string values like this:\n\n```elixir\nconn\n|\u003e assign_errors(%{\n  name: \"Name can't be blank\",\n  password: \"Password must be at least 5 characters\"\n})\n```\n\n## Flash messages\n\nThis library automatically includes Phoenix flash data in Inertia props, under the `flash` key.\n\nFor example, given the following controller action:\n\n```elixir\ndef update(conn, params) do\n  case MyApp.Settings.update(params) do\n    {:ok, _settings} -\u003e\n      conn\n      |\u003e put_flash(:info, \"Settings updated\")\n      |\u003e redirect(to: ~p\"/settings\")\n\n    {:error, changeset} -\u003e\n      conn\n      |\u003e assign_errors(changeset)\n      |\u003e redirect(to: ~p\"/settings\")\n  end\nend\n```\n\nWhen Inertia (or the browser) redirects to the `/settings` page, the Inertia component will receive the flash props:\n\n```javascript\n{\n  \"component\": \"...\",\n  \"props\": {\n    \"flash\": {\n      \"info\": \"Settings updated\"\n    },\n    // ...\n  }\n}\n```\n\n## CSRF protection\n\nThis library automatically sets the `XSRF-TOKEN` cookie for use by the Axios client on the front-end. Since Phoenix expects to receive the CSRF token via the `x-csrf-token` header, you'll need to configure Axios in your front-end JavaScript to use that header name:\n\n```javascript\n// assets/js/app.js\n\nimport axios from \"axios\";\naxios.defaults.xsrfHeaderName = \"x-csrf-token\";\n\n// the rest of your Inertia client code...\n```\n\n## History\n\n**Requires Inertia v2.x on the client-side**.\n\n### Encryption\n\nIf your page props contain sensitive data (such as information about the currently-authenticated user), you can opt to encrypt the history data that's cached in the browser.\n\n```elixir\nconn\n|\u003e encrypt_history()\n```\n\nYou can also enable history encryption globally in your application config:\n\n```elixir\nconfig :inertia,\n  history: [encrypt: true]\n```\n\n### Clearing history\n\nTo instruct the client to clear this history (for example, when a user logs out), you can use the `clear_history/1` helper when building your response.\n\n```elixir\nconn\n|\u003e clear_history()\n```\n\n## Testing\n\nThe `Inertia.Testing` module includes helpers for testing your Inertia controller responses, such as the `inertia_component/1` and `inertia_props/1` functions.\n\n\n```elixir\nuse MyAppWeb.ConnCase\n\nimport Inertia.Testing\n\ndescribe \"GET /\" do\n  test \"renders the home page\", %{conn: conn} do\n    conn = get(\"/\")\n    assert inertia_component(conn) == \"Home\"\n    assert %{user: %{id: 1}} = inertia_props(conn)\n  end\nend\n```\n\n```elixir\nuse MyAppWeb.ConnCase\n\nimport Inertia.Testing\n\ndescribe \"POST /users\" do\n  test \"fails when name empty\", %{conn: conn} do\n    conn = post(\"/users\", %{\"name\" =\u003e \"\"})\n\n    assert %{user: %{id: 1}} = inertia_props(conn)\n    assert redirected_to(conn) == ~p\"/users\"\n    assert inertia_errors(conn) == %{\"name\" =\u003e \"can't be blank\"}\n  end\nend\n```\n\nWe recommend importing `Inertia.Testing` in your `ConnCase` helper, so that it will be at the ready for all your controller tests:\n\n```elixir\ndefmodule MyApp.ConnCase do\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      import Inertia.Testing\n\n      # ...\n    end\n  end\nend\n```\n\n## Server-side rendering\n\nThe Inertia.js client library comes with with server-side rendering (SSR) support, which means you can have your Inertia-powered client hydrate HTML that has been pre-rendered on the server (instead of performing the initial DOM rendering).\n\n\u003e [!NOTE]\n\u003e The steps for enabling SSR in Phoenix are similar to other backend frameworks, but instead of running a separate Node.js server process to render HTML, this library spins up a pool of Node.js process workers to handle SSR calls and manages the state of those node processes from your Elixir process tree. This is mostly just an implementation detail that you don't need to be concerned about, but we'll highlight how our `ssr.js` script differs from the Inertia.js docs.\n\n### Add a server-side rendering module\n\nYou'll need to create a JavaScript module that exports a `render` function to perform the actual server-side rendering of pages. For the purpose of these instructions, we'll assume you're using React. The steps would be similar for other front-end environments supported by Inertia.js, such as [Vue](https://github.com/CallumVass/inertia_vue) and [Svelte](https://github.com/tonydangblog/phoenix-inertia-svelte).\n\nSuppose your main `app.jsx` file looks something like this:\n\n```js\n// assets/js/app.jsx\n\nimport React from \"react\";\nimport { createInertiaApp } from \"@inertiajs/react\";\nimport { createRoot } from \"react-dom/client\";\n\ncreateInertiaApp({\n  resolve: async (name) =\u003e {\n    return await import(`./pages/${name}.jsx`);\n  },\n  setup({ App, el, props }) {\n    createRoot(el).render(\u003cApp {...props} /\u003e);\n  },\n});\n```\n\nYou'll need to create a second JavaScript file (alongside your `app.jsx`) that exports a `render` function. Let's name it `ssr.jsx`.\n\n```js\n// assets/js/ssr.jsx\n\nimport React from \"react\";\nimport ReactDOMServer from \"react-dom/server\";\nimport { createInertiaApp } from \"@inertiajs/react\";\n\nexport function render(page) {\n  return createInertiaApp({\n    page,\n    render: ReactDOMServer.renderToString,\n    resolve: async (name) =\u003e {\n      return await import(`./pages/${name}.jsx`);\n    },\n    setup: ({ App, props }) =\u003e \u003cApp {...props} /\u003e,\n  });\n}\n```\n\nThis is similar to the server entry-point [documented here](https://inertiajs.com/server-side-rendering#add-server-entry-point), except we are simply **exporting a function called `render`**, instead of starting a Node.js server process.\n\nNext, configure esbuild to compile the `ssr.jsx` bundle.\n\n```diff\n  # config/config.exs\n\n  config :esbuild,\n    version: \"0.21.5\",\n    app: [\n      args: ~w(js/app.jsx --bundle --target=es2020 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),\n      cd: Path.expand(\"../assets\", __DIR__),\n      env: %{\"NODE_PATH\" =\u003e Path.expand(\"../deps\", __DIR__)}\n    ],\n+   ssr: [\n+     args: ~w(js/ssr.jsx --bundle --platform=node --outdir=../priv --format=cjs),\n+     cd: Path.expand(\"../assets\", __DIR__),\n+     env: %{\"NODE_PATH\" =\u003e Path.expand(\"../deps\", __DIR__)}\n+   ]\n```\n\nAdd the `ssr` build to the watchers in your dev environment, alongside the other asset watchers:\n\n```diff\n  # config/dev.exs\n  config :my_app, MyAppWeb.Endpoint,\n    # Binding to loopback ipv4 address prevents access from other machines.\n    # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.\n    http: [ip: {127, 0, 0, 1}, port: 4000],\n    check_origin: false,\n    code_reloader: true,\n    debug_errors: true,\n    secret_key_base: \"4Z2yyTu6Uy8AM+MguG3oldEf4aIdswR2BsCm1OtqDK0lEv++T02KktRaXfMbC/Zs\",\n    watchers: [\n      esbuild: {Esbuild, :install_and_run, [:app, ~w(--sourcemap=inline --watch)]},\n+     ssr: {Esbuild, :install_and_run, [:ssr, ~w(--sourcemap=inline --watch)]},\n      tailwind: {Tailwind, :install_and_run, [:my_app, ~w(--watch)]}\n    ]\n```\n\nAdd the `ssr` build step to the asset build and deploy scripts.\n\n```diff\n  # mix.exs\n\n  defp aliases do\n    [\n      setup: [\"deps.get\", \"ecto.setup\", \"assets.setup\", \"assets.build\"],\n      \"ecto.setup\": [\"ecto.create\", \"ecto.migrate\", \"run priv/repo/seeds.exs\"],\n      \"ecto.reset\": [\"ecto.drop\", \"ecto.setup\"],\n      test: [\"ecto.create --quiet\", \"ecto.migrate --quiet\", \"test\"],\n      \"assets.setup\": [\"tailwind.install --if-missing\", \"esbuild.install --if-missing\"],\n-     \"assets.build\": [\"tailwind app\", \"esbuild app\"],\n+     \"assets.build\": [\"tailwind app\", \"esbuild app\", \"esbuild ssr\"],\n      \"assets.deploy\": [\n        \"tailwind app --minify\",\n        \"esbuild app --minify\",\n+       \"esbuild ssr\",\n        \"phx.digest\"\n      ]\n    ]\n  end\n```\n\nAs configured, this will place the generated `ssr.js` bundle into the `priv` directory. Since it's generated code, add it to your `.gitignore` file.\n\n```diff\n  # .gitignore\n\n+ /priv/ssr.js\n```\n\n### Configuring your app for server-rendering\n\nNow that you have a Node.js module capable of server-rendering your pages, youll need to tell the Inertia.js Phoenix library to perform SSR.\n\nFirst, add the `Inertia.SSR` module to your application's supervision tree.\n\n```diff\n  # lib/my_app/application.ex\n\n  defmodule MyApp.Application do\n    use Application\n\n    @impl true\n    def start(_type, _args) do\n      children = [\n        MyAppWeb.Telemetry,\n        MyApp.Repo,\n        {DNSCluster, query: Application.get_env(:MyApp, :dns_cluster_query) || :ignore},\n        {Phoenix.PubSub, name: MyApp.PubSub},\n        # Start the Finch HTTP client for sending emails\n        {Finch, name: MyApp.Finch},\n        # Start a worker by calling: MyApp.Worker.start_link(arg)\n        # {MyApp.Worker, arg},\n\n+       # Start the SSR process pool\n+       # You must specify a `path` option to locate the directory where the `ssr.js` file lives.\n+       {Inertia.SSR, path: Path.join([Application.app_dir(:my_app), \"priv\"])},\n\n        # Start to serve requests, typically the last entry\n        MyAppWeb.Endpoint,\n      ]\n```\n\nThen, update your config to enable SSR (if you'd like to enable it globally).\n\n```diff\n  # config/config.exs\n\n  config :inertia,\n    # The Phoenix Endpoint module for your application. This is used for building\n    # asset URLs to compute a unique version hash to track when something has\n    # changed (and a reload is required on the frontend).\n    endpoint: MyAppWeb.Endpoint,\n\n    # An optional list of static file paths to track for changes. You'll generally\n    # want to include any JavaScript assets that may require a page refresh when\n    # modified.\n    static_paths: [\"/assets/app.js\"],\n\n    # The default version string to use (if you decide not to track any static\n    # assets using the `static_paths` config). Defaults to \"1\".\n    default_version: \"1\",\n\n    # Enable server-side rendering for page responses (requires some additional setup,\n    # see instructions below). Defaults to `false`.\n-   ssr: false\n+   ssr: true\n\n    # Whether to raise an exception when server-side rendering fails (only applies\n    # when SSR is enabled). Defaults to `true`.\n    #\n    # Recommended: enable in non-production environments and disable in production,\n    # so that SSR failures will not cause 500 errors (but instead will fallback to\n    # CSR).\n    raise_on_ssr_failure: config_env() != :prod\n```\n\n### Installing Node.js in your production\n\nYou need to have Node.js installed in your production server environment, so that we can call the SSR script when serving pages. These steps assume you are deploying your application using a Dockerfile and releases.\n\nIf you haven't installed node into your runner image, add the following command to your Dockerfile (after the `FROM ${RUNNER_IMAGE}` step).\n\n```diff\n  FROM ${RUNNER_IMAGE}\n\n  # install curl (and a few other packages)\n  RUN apt-get update -y \u0026\u0026 \\\n-     apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates \u0026\u0026 \\\n+     apt-get install -y libstdc++6 openssl curl libncurses5 locales ca-certificates \u0026\u0026 \\\n      apt-get clean \u0026\u0026 rm -f /var/lib/apt/lists/*_*\n\n  # install Node.js\n+ RUN curl -fsSL https://deb.nodesource.com/setup_x.x | bash - \u0026\u0026 \\\n+    apt-get update \u0026\u0026 \\\n+    apt-get install -y nodejs\n\n  # ...\n\n  ENV MIX_ENV=\"prod\"\n\n  # ensure node is running in production mode\n+ ENV NODE_ENV=\"production\"\n```\n\n\u003e [!IMPORTANT]\n\u003e **Be sure to set `NODE_ENV=production`**, so that the SSR script is cached in memory. Otherwise, your page rendering times will be very slow!\n\n### Client side hydration\n\n[Follow the instructions from the Inertia.js docs](https://inertiajs.com/server-side-rendering#client-side-hydration) for updating your client-side code to hydrate the pre-rendered HTML coming from the server.\n\nUsing our example React script from above, the adaptation looks like this:\n\n```diff\n  // assets/js/app.jsx\n\n  import React from \"react\";\n  import { createInertiaApp } from \"@inertiajs/react\";\n- import { createRoot } from \"react-dom/client\";\n+ import { hydrateRoot } from \"react-dom/client\";\n\n  createInertiaApp({\n    resolve: async (name) =\u003e {\n      return await import(`./pages/${name}.jsx`);\n    },\n    setup({ App, el, props }) {\n-     createRoot(el).render(\u003cApp {...props} /\u003e);\n+     hydrateRoot(el, \u003cApp {...props} /\u003e);\n    },\n  });\n```\n\n---\n\nMaintained by the team at [SavvyCal](https://savvycal.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finertiajs%2Finertia-phoenix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finertiajs%2Finertia-phoenix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finertiajs%2Finertia-phoenix/lists"}