{"id":24822352,"url":"https://github.com/gsmlg-dev/phoenix-react","last_synced_at":"2026-03-11T17:01:01.814Z","repository":{"id":273734262,"uuid":"920597054","full_name":"gsmlg-dev/phoenix-react","owner":"gsmlg-dev","description":"React Render for Phoenix Framework","archived":false,"fork":false,"pushed_at":"2025-02-16T12:57:28.000Z","size":5672,"stargazers_count":52,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-25T21:47:50.872Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gsmlg-dev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2025-01-22T12:44:22.000Z","updated_at":"2025-03-15T11:24:27.000Z","dependencies_parsed_at":"2025-01-23T03:17:54.480Z","dependency_job_id":"0bc7f08c-875b-43f9-b5fc-5859a7e52f8e","html_url":"https://github.com/gsmlg-dev/phoenix-react","commit_stats":null,"previous_names":["gsmlg/phoenix-react","gsmlg-dev/phoenix-react"],"tags_count":9,"template":false,"template_full_name":"gsmlg/elixir-package-template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gsmlg-dev%2Fphoenix-react","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gsmlg-dev%2Fphoenix-react/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gsmlg-dev%2Fphoenix-react/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gsmlg-dev%2Fphoenix-react/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gsmlg-dev","download_url":"https://codeload.github.com/gsmlg-dev/phoenix-react/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248505929,"owners_count":21115354,"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":[],"created_at":"2025-01-30T18:54:00.645Z","updated_at":"2026-03-11T17:01:01.807Z","avatar_url":"https://github.com/gsmlg-dev.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Phoenix.ReactServer\n\n[![CI](https://github.com/gsmlg-dev/phoenix-react/actions/workflows/ci.yml/badge.svg)](https://github.com/gsmlg-dev/phoenix-react/actions/workflows/ci.yml)\n[![Hex.pm](https://img.shields.io/hexpm/v/phoenix_react_server.svg)](https://hex.pm/packages/phoenix_react_server)\n[![Hexdocs.pm](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/phoenix_react_server/)\n[![Hex.pm](https://img.shields.io/hexpm/dt/phoenix_react_server.svg)](https://hex.pm/packages/phoenix_react_server)\n[![Hex.pm](https://img.shields.io/hexpm/dw/phoenix_react_server.svg)](https://hex.pm/packages/phoenix_react_server)\n\nPhoenix.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.\n\n## ✨ Features\n\n- **🎨 Multiple Rendering Methods**: Support for `renderToStaticMarkup`, `renderToString`, and `renderToReadableStream`\n- **⚡ Dual Runtime Support**: Choose between Bun and Deno runtimes for optimal performance\n- **🔄 Client-Side Hydration**: Full support for React hydration with Phoenix LiveView\n- **💾 Intelligent Caching**: Built-in caching system with configurable TTL\n- **👀 File Watching**: Automatic component reloading in development\n- **🔒 Type Safety**: Comprehensive type specifications and documentation\n- **🚀 Production Ready**: Optimized for release deployments with bundled assets\n\n## 📚 Documentation\n\nSee the [complete documentation](https://hexdocs.pm/phoenix_react_server/) for detailed API reference and examples.\n\n## 🚀 Installation\n\nAdd `phoenix_react_server` to your dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:phoenix_react_server, \"~\u003e 0.7\"}\n  ]\nend\n```\n\n## ⚙️ Configuration\n\nConfigure Phoenix.ReactServer in your application config:\n\n```elixir\nimport Config\n\nconfig :phoenix_react_server, Phoenix.ReactServer,\n  # React runtime (default: Phoenix.ReactServer.Runtime.Bun)\n  runtime: Phoenix.ReactServer.Runtime.Bun,\n  # React component base path\n  component_base: Path.expand(\"../assets/component\", __DIR__),\n  # Render timeout in milliseconds (default: 5_000)\n  render_timeout: 5_000,\n  # Cache TTL in seconds (default: 60)\n  cache_ttl: 60\n```\n\n### Supported Runtimes\n\n- **Bun Runtime** (`Phoenix.ReactServer.Runtime.Bun`) - Fast JavaScript runtime with built-in bundler\n- **Deno Runtime** (`Phoenix.ReactServer.Runtime.Deno`) - Secure JavaScript runtime with TypeScript support\n\n### Deno Runtime Configuration\n\nTo use Deno instead of Bun, configure the runtime and its specific settings:\n\n```elixir\nconfig :phoenix_react_server, Phoenix.ReactServer,\n  runtime: Phoenix.ReactServer.Runtime.Deno,\n  component_base: Path.expand(\"../assets/component\", __DIR__),\n  cache_ttl: 60\n\n# Deno-specific configuration\nconfig :phoenix_react_server, Phoenix.ReactServer.Runtime.Deno,\n  cmd: System.find_executable(\"deno\"),\n  server_js: Path.expand(\"../priv/react/server.js\", __DIR__),\n  port: 5125,\n  env: :dev  # Use :prod for production\n```\n\n#### Deno Requirements\n- **Deno 2.x** (recommended)\n- Components must use `.jsx` file extension for proper JSX parsing\n- Deno automatically downloads npm packages via `--node-modules-dir` flag\n\n#### Environment Variable Switching\n\nYou can use environment variables to switch runtimes dynamically:\n\n```elixir\nruntime =\n  case System.get_env(\"REACT_RUNTIME\", \"bun\") do\n    \"bun\" -\u003e Phoenix.ReactServer.Runtime.Bun\n    \"deno\" -\u003e Phoenix.ReactServer.Runtime.Deno\n    _ -\u003e Phoenix.ReactServer.Runtime.Bun\n  end\n\nconfig :phoenix_react_server, Phoenix.ReactServer, runtime: runtime\n```\n\n### Application Setup\n\nAdd the React render server to your application's Supervisor tree:\n\n```elixir\ndef start(_type, _args) do\n  children = [\n    ReactDemoWeb.Telemetry,\n    {DNSCluster, query: Application.get_env(:react_demo, :dns_cluster_query) || :ignore},\n    {Phoenix.PubSub, name: ReactDemo.PubSub},\n    # React render service\n    Phoenix.ReactServer,\n    ReactDemoWeb.Endpoint\n  ]\n\n  opts = [strategy: :one_for_one, name: ReactDemo.Supervisor]\n  Supervisor.start_link(children, opts)\nend\n```\n\n### Creating React Components\n\nCreate a Phoenix component module to wrap your React components:\n\n```elixir\ndefmodule ReactDemoWeb.ReactComponents do\n  use Phoenix.Component\n  import Phoenix.ReactServer.Helper\n\n  def react_markdown(assigns) do\n    {static, props} = Map.pop(assigns, :static, true)\n\n    react_component(%{\n      component: \"markdown\",\n      props: props,\n      static: static\n    })\n  end\nend\n```\n\nImport your React components in the HTML helpers:\n\n```elixir\ndefp html_helpers do\n  quote do\n    # Translation\n    use Gettext, backend: ReactDemoWeb.Gettext\n\n    # HTML escaping functionality\n    import Phoenix.HTML\n    # Core UI components\n    import ReactDemoWeb.CoreComponents\n    import ReactDemoWeb.ReactComponents\n\n    # ... other imports\n  end\nend\n```\n\n## 🎯 Rendering Methods\n\n### Static Markup Rendering\n\nUse `render_to_static_markup` for SEO-friendly content that doesn't need client-side interaction:\n\n```html\n\u003cdiv class=\"card\"\u003e\n  \u003cdiv class=\"card-body\"\u003e\n    \u003cdiv class=\"card-title\"\u003eHello There\u003c/div\u003e\n    \u003c.react_markdown data={@data} /\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n### String Rendering with Hydration\n\nUse `render_to_string` when you need client-side hydration:\n\n```html\n\u003cdiv class=\"card w-full\"\u003e\n  \u003cdiv class=\"card-body\"\u003e\n    \u003ch3 class=\"card-title\"\u003e\n      This \u003ccode class=\"text-primary\"\u003eTable\u003c/code\u003e is rendered with \u003ccode class=\"text-secondary\"\u003ereact-dom/server\u003c/code\u003e\n    \u003c/h3\u003e\n    \u003c!-- Notice: Remove whitespace to avoid breaking hydration --\u003e\n    \u003c!-- Add `phx-no-format` to prevent mix format from changing this --\u003e\n    \u003cdiv\n      id=\"system_usage_container\"\n      class=\"w-full h-full\"\n      phx-no-format\n    \u003e\u003c.react_system_stats data={@data} /\u003e\u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n#### Client-Side Hydration\n\n```javascript\ndocument.addEventListener('DOMContentLoaded', function() {\n  const store = new Store();\n  const domContainer = document.querySelector('#system_usage_container');\n  \n  if (domContainer) {\n    let channel = socket.channel(\"system_usage:lobby\", {});\n\n    channel.join()\n      .receive(\"ok\", resp =\u003e console.log(\"Joined successfully\", resp))\n      .receive(\"error\", resp =\u003e console.log(\"Unable to join\", resp));\n\n    function Usage(props) {\n      const data = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot);\n      return \u003cSystemUsage data={data} /\u003e;\n    }\n\n    channel.on(\"joined\", (data) =\u003e {\n      store.reset(data.data);\n      requestAnimationFrame(() =\u003e {\n        hydrateRoot(domContainer, \u003cUsage /\u003e);\n      });\n    });\n\n    channel.on(\"stats\", (data) =\u003e {\n      store.unshift(data);\n    });\n  }\n});\n```\n\n### Streaming Rendering\n\nUse `render_to_readable_stream` for large components or LiveView integration:\n\n```html\n\u003cdiv\n  id=\"react-live-form\"\n  class=\"w-full h-full\"\n  phx-update=\"ignore\"\n  phx-hook=\"LiveFormHook\"\n\u003e\u003c.react_live_form data={@form_data} /\u003e\u003c/div\u003e\n```\n\n#### LiveView Integration\n\n```javascript\nconst hooks = {\n  LiveFormHook: {\n    mounted() {\n      const formState = new FormState();\n\n      formState.setData = (data) =\u003e {\n        this.pushEvent(\"form:input\", data);\n      };\n\n      function LiveViewForm(props) {\n        const data = useSyncExternalStore(formState.subscribe, formState.getSnapshot, formState.getServerSnapshot);\n        return \u003cLiveForm data={data} setData={formState.setData} /\u003e;\n      }\n\n      this.pushEvent(\"form:init\", {}, (data, ref) =\u003e {\n        formState.reset(data);\n        this.reactRoot = hydrateRoot(this.el, \u003cLiveViewForm /\u003e);\n      });\n      \n      this.handleEvent(\"form:update\", (data) =\u003e {\n        formState.assign(data);\n      });\n    },\n  }\n}\n```\n\n## 🚀 Production Deployment\n\n### Bundling Components\n\nBundle your React components with the server for production deployment:\n\n#### Bun Runtime\n```shell\nmix phx.react.bun.bundle --component-base=assets/component --output=priv/react/server.js\n```\n\n#### Deno Runtime\n```shell\nmix phx.react.deno.bundle --component-base=assets/component --output=priv/react/server.js\n```\n\n### Production Configuration\n\nConfigure the runtime for production in `runtime.exs`:\n\n```elixir\n# For Bun runtime\nconfig :phoenix_react_server, Phoenix.ReactServer.Runtime.Bun,\n  cmd: System.find_executable(\"bun\"),\n  server_js: Path.expand(\"../priv/react/server.js\", __DIR__),\n  port: 12666,\n  env: :prod\n\n# For Deno runtime\nconfig :phoenix_react_server, Phoenix.ReactServer.Runtime.Deno,\n  cmd: System.find_executable(\"deno\"),\n  server_js: Path.expand(\"../priv/react/server.js\", __DIR__),\n  port: 12667,\n  env: :prod\n```\n\n## 🌐 CDN Hydration\n\nHydrate React components on the client side using CDN modules:\n\n```html\n\u003cscript type=\"importmap\"\u003e\n  {\n    \"imports\": {\n      \"react-dom\": \"https://esm.run/react-dom@19\",\n      \"app\": \"https://my.web.site/app.js\"\n    }\n  }\n\u003c/script\u003e\n\u003cscript type=\"module\"\u003e\nimport { hydrateRoot } from 'react-dom/client';\nimport { Component } from 'app';\n\nhydrateRoot(\n  document.getElementById('app-wrapper'),\n  \u003cApp /\u003e\n);\n\u003c/script\u003e\n```\n\n## 🎮 Demo\n\nA complete demo application is available in the `./react_demo` directory, showcasing:\n- All rendering methods\n- LiveView integration\n- Client-side hydration\n- File watching in development\n- Production bundling\n\n## 🔧 Advanced Configuration\n\n### Caching Strategy\n\nConfigure caching behavior for optimal performance:\n\n```elixir\nconfig :phoenix_react_server, Phoenix.ReactServer,\n  cache_ttl: 300,  # 5 minutes cache\n  gc_time: 60_000  # Cleanup interval in milliseconds\n```\n\n### Security Settings\n\nConfigure security limits for component rendering:\n\n```elixir\nconfig :phoenix_react_server, Phoenix.React.Config,\n  security: %{\n    max_component_name_length: 100,\n    max_request_size: 1_048_576,  # 1MB\n    request_timeout_ms: 30_000\n  }\n```\n\n### File Watching\n\nConfigure development file watching:\n\n```elixir\nconfig :phoenix_react_server, Phoenix.React.Config,\n  file_watcher: %{\n    throttle_ms: 3000,\n    debounce_ms: 100\n  }\n```\n\n## 🤝 Contributing\n\nContributions 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.\n\n## 📄 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgsmlg-dev%2Fphoenix-react","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgsmlg-dev%2Fphoenix-react","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgsmlg-dev%2Fphoenix-react/lists"}