| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Phoenix.ReactServer is a powerful library that enables server-side rendering of React components within Phoenix applications. It provides seamless integration between React and Phoenix, supporting multiple rendering methods and runtime environments.
See the complete documentation for detailed API reference and examples.
Add phoenix_react_server to your dependencies in mix.exs:
def deps do
[
{:phoenix_react_server, "~> 0.7"}
]
endConfigure Phoenix.ReactServer in your application config:
import Config
config :phoenix_react_server, Phoenix.ReactServer,
# React runtime (default: Phoenix.ReactServer.Runtime.Bun)
runtime: Phoenix.ReactServer.Runtime.Bun,
# React component base path
component_base: Path.expand("../assets/component", __DIR__),
# Render timeout in milliseconds (default: 5_000)
render_timeout: 5_000,
# Cache TTL in seconds (default: 60)
cache_ttl: 60To use Deno instead of Bun, configure the runtime and its specific settings:
config :phoenix_react_server, Phoenix.ReactServer,
runtime: Phoenix.ReactServer.Runtime.Deno,
component_base: Path.expand("../assets/component", __DIR__),
cache_ttl: 60
# Deno-specific configuration
config :phoenix_react_server, Phoenix.ReactServer.Runtime.Deno,
cmd: System.find_executable("deno"),
server_js: Path.expand("../priv/react/server.js", __DIR__),
port: 5125,
env: :dev # Use :prod for productionYou can use environment variables to switch runtimes dynamically:
runtime =
case System.get_env("REACT_RUNTIME", "bun") do
"bun" -> Phoenix.ReactServer.Runtime.Bun
"deno" -> Phoenix.ReactServer.Runtime.Deno
_ -> Phoenix.ReactServer.Runtime.Bun
end
config :phoenix_react_server, Phoenix.ReactServer, runtime: runtimeAdd the React render server to your application's Supervisor tree:
def start(_type, _args) do
children = [
ReactDemoWeb.Telemetry,
{DNSCluster, query: Application.get_env(:react_demo, :dns_cluster_query) || :ignore},
{Phoenix.PubSub, name: ReactDemo.PubSub},
# React render service
Phoenix.ReactServer,
ReactDemoWeb.Endpoint
]
opts = [strategy: :one_for_one, name: ReactDemo.Supervisor]
Supervisor.start_link(children, opts)
endCreate a Phoenix component module to wrap your React components:
defmodule ReactDemoWeb.ReactComponents do
use Phoenix.Component
import Phoenix.ReactServer.Helper
def react_markdown(assigns) do
{static, props} = Map.pop(assigns, :static, true)
react_component(%{
component: "markdown",
props: props,
static: static
})
end
endImport your React components in the HTML helpers:
defp html_helpers do
quote do
# Translation
use Gettext, backend: ReactDemoWeb.Gettext
# HTML escaping functionality
import Phoenix.HTML
# Core UI components
import ReactDemoWeb.CoreComponents
import ReactDemoWeb.ReactComponents
# ... other imports
end
endUse render_to_static_markup for SEO-friendly content that doesn't need client-side interaction:
<div class="card">
<div class="card-body">
<div class="card-title">Hello There</div>
<.react_markdown data={@data} />
</div>
</div>Use render_to_string when you need client-side hydration:
<div class="card w-full">
<div class="card-body">
<h3 class="card-title">
This <code class="text-primary">Table</code> is rendered with <code class="text-secondary">react-dom/server</code>
</h3>
<!-- Notice: Remove whitespace to avoid breaking hydration -->
<!-- Add `phx-no-format` to prevent mix format from changing this -->
<div
id="system_usage_container"
class="w-full h-full"
phx-no-format
><.react_system_stats data={@data} /></div>
</div>
</div>document.addEventListener('DOMContentLoaded', function() {
const store = new Store();
const domContainer = document.querySelector('#system_usage_container');
if (domContainer) {
let channel = socket.channel("system_usage:lobby", {});
channel.join()
.receive("ok", resp => console.log("Joined successfully", resp))
.receive("error", resp => console.log("Unable to join", resp));
function Usage(props) {
const data = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot);
return <SystemUsage data={data} />;
}
channel.on("joined", (data) => {
store.reset(data.data);
requestAnimationFrame(() => {
hydrateRoot(domContainer, <Usage />);
});
});
channel.on("stats", (data) => {
store.unshift(data);
});
}
});Use render_to_readable_stream for large components or LiveView integration:
<div
id="react-live-form"
class="w-full h-full"
phx-update="ignore"
phx-hook="LiveFormHook"
><.react_live_form data={@form_data} /></div>const hooks = {
LiveFormHook: {
mounted() {
const formState = new FormState();
formState.setData = (data) => {
this.pushEvent("form:input", data);
};
function LiveViewForm(props) {
const data = useSyncExternalStore(formState.subscribe, formState.getSnapshot, formState.getServerSnapshot);
return <LiveForm data={data} setData={formState.setData} />;
}
this.pushEvent("form:init", {}, (data, ref) => {
formState.reset(data);
this.reactRoot = hydrateRoot(this.el, <LiveViewForm />);
});
this.handleEvent("form:update", (data) => {
formState.assign(data);
});
},
}
}Bundle your React components with the server for production deployment:
mix phx.react.bun.bundle --component-base=assets/component --output=priv/react/server.jsmix phx.react.deno.bundle --component-base=assets/component --output=priv/react/server.jsConfigure the runtime for production in runtime.exs:
# For Bun runtime
config :phoenix_react_server, Phoenix.ReactServer.Runtime.Bun,
cmd: System.find_executable("bun"),
server_js: Path.expand("../priv/react/server.js", __DIR__),
port: 12666,
env: :prod
# For Deno runtime
config :phoenix_react_server, Phoenix.ReactServer.Runtime.Deno,
cmd: System.find_executable("deno"),
server_js: Path.expand("../priv/react/server.js", __DIR__),
port: 12667,
env: :prodHydrate React components on the client side using CDN modules:
<script type="importmap">
{
"imports": {
"react-dom": "https://esm.run/react-dom@19",
"app": "https://my.web.site/app.js"
}
}
</script>
<script type="module">
import { hydrateRoot } from 'react-dom/client';
import { Component } from 'app';
hydrateRoot(
document.getElementById('app-wrapper'),
<App />
);
</script>A complete demo application is available in the ./react_demo directory, showcasing:
Configure caching behavior for optimal performance:
config :phoenix_react_server, Phoenix.ReactServer,
cache_ttl: 300, # 5 minutes cache
gc_time: 60_000 # Cleanup interval in millisecondsConfigure security limits for component rendering:
config :phoenix_react_server, Phoenix.React.Config,
security: %{
max_component_name_length: 100,
max_request_size: 1_048_576, # 1MB
request_timeout_ms: 30_000
}Configure development file watching:
config :phoenix_react_server, Phoenix.React.Config,
file_watcher: %{
throttle_ms: 3000,
debounce_ms: 100
}Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the MIT License - see the LICENSE file for details.
| Back | FazBrowse Home | New Git URL |