defmodule ReactPhoenix.ServerSide do @moduledoc """ Functions providing server-side rendering of React components. ## What? Why? Server-side rendering of React components can be importantant for things such as Search Engine Optimization. In short, we have our Phoenix controller gather the HTML that would be generated by a React component (including utilizing any props required) and pass that directly into the view template. Once there, we can render the HTML string directly on initial page load so that loading is fast and data is there when a user visits your page (or a search engine crawler finds it). ## Configuration In order to get this working correctly, you need to take a few extra steps beyond the setup for client-side rendering. Add `:react_phoenix` to your `applications` list. ### `mix.exs` ``` def application do [mod: {YourApp, []}, applications: [..., :react_phoenix] ] end ``` We eventually will need to compile your React components down into commonjs files so our renderer can understand it. Choose a directory for those compiled components to live in (and remember it because you'll need it in a few different spots) and let ReactPhoenix know about it. You do that in your `config/config.exs` file: ### `config/config.exs` ``` config :react_phoenix, compiled_path: Path.join(["priv", "static", "js", "components"]) ``` This tells our renderer to look for the component in the `priv/static/js/components` directory of our project. #### Custom react-stdio location _If you are using a standard Phoenix 1.2 or 1.3 app structure, you can skip this section._ You may need to specify the route of your react-stdio module if your node_modules directory does not live in the root directory of your project *(if your ReactPhoenix project lives within an umbrella project, for example)*. To do that, add a `react_stdio_path` config option in your `config/config.exs` file. ``` config :react_phoenix, compiled_path: Path.join(["priv", "static", "js", "components"]), # defaults to node_modules/.bin/react-stdio react_stdio_path: Path.join(["path_to_your_app", "node_modules", ".bin", "react-stdio"]) ``` Then run `mix deps.clean react_phoenix && mix deps.get`. #### Set up the watcher Configure your Endpoint to watch for new component files and compile them down as it finds them (but only in dev). If you have a standard Phoenix setup, you likely already have at least one watcher set up for your Endpoint. *Be careful not to overwrite* anything you currently have configured, but to add a new watcher. ### `config/dev.exs` ``` config :your_app, YourApp.Endpoint, watchers: [ node: [ # This is likely already there "node_modules/brunch/bin/brunch", "watch", "--stdin", cd: Path.expand("../", __DIR__) ], node: [ # We need to add this new one "node_modules/babel-cli/bin/babel.js", "--watch", # where your React components are saved (before compilation) "web/static/js/components", # where you'd like to store them after compilation. # This MUST match your compiled_path config from config/config.exs. "--out-dir", "priv/static/js/components", "--presets=env,react" ] ] ``` Add the same command to your deploy script (after `brunch build --production`). ### `package.json` ``` { // ... "scripts": { "deploy": "brunch build --production; babel web/static/js/components --out-dir priv/static/js/components --presets=env,react", "watch": "brunch watch --stdin" }, // ... } ``` ## Usage In your Phoenix controller, you can get the rendered HTML and pass that on to your views. ``` def YourApp.PageController do use YourApp.Web, :controller def index(conn, _params) do people = ["Joe", "Robert", "Other Joe"] html = ReactPhoenix.ServerSide.react_component("hello", %{people: people}) render(conn, "index.html", react_html: html, people: people) end end ``` Once you are in your view, you can render that HTML and then mount the component client-side with the `target_id` option. See `ReactPhoenix.ClientSide` for more information. ```