Current section
Files
Jump to
Current section
Files
lib/react_phoenix/server_side.ex
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.
```
<div id="hello"><%= @react_html %></div>
<%= ReactPhoenix.ClientSide.react_component("Components.Hello", %{people: @people}, target_id: "hello") %>
```
"""
@path_prefix Application.get_env(:react_phoenix, :compiled_path)
@doc """
Generate a String that contains the HTML of a rendered React component.
It returns a String containing HTML that is suitable for passing to a Phoenix view for rendering.
`filename` should be, along with the `path_prefix` configured for your application,
where your compiled React component can be found (minus the `.js` filename extension).
For example, if you configured your app with `config :react_phoenix, compiled_path: "priv/static/js/components"`
and you have a component file compiled there with the name of `hello_react.js`, you would pass `"hello_react"` as `filename`.
"""
def react_component(filename, props \\ %{}) do
path = Path.join(@path_prefix, "#{filename}.js")
ReactPhoenix.ReactIo.json_call!(%{
component: path,
props: props
})
|> Map.get("html")
end
end