ReactRender: Server Side Rendering for React Components

Hi, I wanted share a small library we at Revelry Labs made for rendering react components from the server side. There are instructions for set up for phoenix projects here.

Give it a try and tell us what you think!

19 Likes

Wow this is very cool, thanks for open sourcing it. I’ve been experimenting with different frontend/backend architectures for a couple of months now leveraging React and lately have been using Next.js to do SSR talking to Phoenix backend using Apollo/Absinthe. It all works (generally) but adds a heavy fast-moving dependency and some degree of complexity.

It might be cleaner/simpler to use this library instead. Two questions:

  • I realize it’s very new and I was only able to find one example app on GH using it (https://github.com/Harleymckee/reactin). Are you using it in dev/prod on some closed-source apps already or is it experimental?
  • I think it uses a GenServer to control a single spawned long-running node instance that does all the rendering. Have you done load testing on it? I think (not sure) each render request blocks all other render requests and not sure what happens if node crashes for a given request. Maybe using poolboy (or something) could make it more performant/resilient… have you considered?

Thanks again!

2 Likes

Thanks! My personal aim was to make it so it didn’t need a node webserver running and that it could be supervised, started, and stopped in an elixir app. I do like Next.js a lot actually. I just wanted to reduce the complexity here. The beauty with this is you can use it for full page renders or just sprinkling in react components.

It’s still new and I think you found my coworker’s test app, haha. We aren’t using it in prod yet, but since we do a lot of react here, it is going to be soon enough.

I haven’t done any load testing on it. You are right that if it’s only one process it will block other requests. We do want to make it have more. I haven’t figured out the mechanism but maybe poolboy is an option. Whatever happens, I want it to be configurable.

1 Like

Haha yeah, the power of GH code searching filename:mix.exs react_render does the trick. I do that a LOT to find examples of library use in the wild. :slight_smile:

Love the fact that you can use it for full page rendering or just sprinkling react components into an EEx template. Was looking at react-phoenix also which only supports CSR at this point before deciding on fully decoupling the frontend using Next.js. I’m being careful with coupling such that I could replace Next.js with this library to reduce some complexity (albeit coupling more tightly with Phoenix) at a later date without a huge rewrite.

Thanks for the info… yeah I will definitely be watching this repo very closely!

1 Like

Hi, version 2.0.0 was published a few days ago, allowing to have multiple renderers. The number of renderers is configurable.

Take a look at the changelog for more details:

6 Likes

Hi @bryanjos,

great library!
How could we combine your library with react styled components on SSR?

Best regards
David

As long as any modules are within the NODE_PATH, it should just work

1 Like

Is someone using this in prod?

1 Like

i have problems loading two instances of react , getting error on using react hooks, with this library

Really impressed with this plugin Bryan, thanks for open sourcing it. I forked it and got it working with Svelte.js.. A suggestion I have is to abstract away the elixir logic from the React stuff (and maybe call it 'elixir_ssr_renderer) to make it easier to adapt it to different JS frameworks.

2 Likes

I have been using a slight variant of this for full page rendering of react apps and its working great. Almost entirely replacing eex templates and relying solely on server side rendered + hydrated react on the client. This pattern is a bit against the grain, and rather than attempting to avoid javascript within Phoenix front-ends, it embraces it!

I have not really built much using eex templates, and generally find myself reaching for react when building decently complex UIs in Phoenix apps. I do have a javascript background, more specifically Meteor, so I suppose it is just what I am more familiar with.

I think for people coming from node, or react backgrounds, this pattern would help ease them into using phoenix on the back-end, and maybe help grow the community!

4 Likes

Just added release functionality to the repo above, and published it to a 5$ Linode instance to test it out.

Check it out here: https://nomrah.dev

I left the dashboard open in production to let people see that too…

The app is behind HAproxy, so IPs of users are not public. Is that enough? Or should i be worried about something else when leaving the dashboard public?

1 Like

@harmon25 Looks great! is this the same model that NextJS et al use? From looking through the code here, the phoenix side will render an empty html page and then render the <ServerContainer /> component which contains the app, hyrdated with data passed from the server. This is then ‘hydrated’ on the client side using the props passed from the server - is this all correct?

My question is then, when is the <ClientContainer /> rendered?

Also once that initial page is rendered, is it pretty much from then on the same as an SPA? E.g. routing will all take place client side and no more SSR will occur?

I’m guessing the main benefit here (I usually do SPA + GQL API Phoenix) is that it eliminates the need for the intial page fetch from CDN (e.g. netlify) -> render -> fetch data from api (show loading states) -> then show data

and rather:

page loads directly from server -> initial data passed to react component -> component rendered already with data ?

Just want to make sure I understand this right - so whilst the actual content is no longer served as a single static JS Bundle (typically to a CDN close to the user), it should be faster because it eliminates the need for CDN call + API call (and most likely CORS) and can instead can all be delivered in a single round trip?

Hi @Harrisonl,
Thanks for checking it out!

Yea it is pretty similar to NextJS, but NextJS is IMO better suited for something fully static, and served entirely from CDN, or maybe the new serverless idea they are pushing. But deploying NextJS as part of an Express application or the like is not recommended, because of the synchronous rendering of React components to a string in Node.

Thing is, I need/want to reach down to the underlying HTTP constructs for security purposes, like cookies, CSRF, redirects, server side auth etc…

This pattern lets me leverage Phoenix/Plug and its great patterns and structure for managing HTTP/API concerns, and delegating the UI/UX to server rendered + client hydrated React. Which despite its pitfalls, has enormous engineering effort/community behind it…

I currently work on a team that has too much React experience to toss it away. As much as I love the idea of LiveView; I also appreciate the inherent separation between front and back-end code the SPA pattern benefits from, and how it allows for teams that can specialize in say React or Elixir.

To directly answer the questions:
<ClientContainer /> is used to hydrate React on the client. This is necessary because certain React provider components, namely react-routers StaticRouter vs BrowserRouter.

The StaticRouter is used for server rendering and pretty much just takes a location(path) as a prop, and figures out what page of the React app to render. The linked example is using a wildcard phoenix route,to pass the conn.request_path into the ServerContainer so it knows what page to render.

The ClientContainer uses BrowserRouter, which is actually hooked up to the browsers push state history stuff and grabs its location from the URL bar.

So yes, the component is rendered on the server with the data you provide it.
During rendering the component on the server, it could also do some data fetching from within the servers node environment.

It is still possible to deliver the app.js, and app.css webpack bundles over CDN, just not the rendered HTML page. And yes, the app can be bootstrapped entirely on the server, removing the need for a initial API call to understand who is on the page or for critical data for example.

The rendered HTML could be cached in Elixir - but depending on the state it is hydrated with - e.g private user information. It might not be a good idea to be naively caching it, it does however have a pretty substantial performance improvement if it was necessary.

Happy to answer any more questions!

Oh, and to clarify phoenix actually returns the rendered react component - so not an empty html page. You can verify this when looking at the data over the network, or the initial page load.

1 Like

Thanks for super detailed reply! That definitely cleared it up.

I’m in a very similar boat RE team with a lot of react experience and am actually a big fan of react itself and don’t want to introduce a completely new technology (LiveView) until it’s more battle-tested with a bigger eco-system behind it. Although I do want to find some ways to increase performance and remove some of the pain’s associated with SPA + API’s for a bit faster dev cycle, hence the digging into SSR react.

Just the last question I had is once the server has loaded the app and rendered the front end, is it for all intents and purposes an SPA? E.g. client side routing and not every route goes through the whole server side rendered flow again. Sorry for the basic questions - never actually done SSR react, always been SPA so some of this stuff is quite new to me!

Yes exactly!
Once the client is hydrated, and you are using BrowserRouter or the like, React just picks up from there.

This works pretty well with the phoenix wildcard, because you can still refresh the page and trigger the server render. But a regular user navigating via react router links etc will be back in full spa land pretty much. You can see this by navigating between the / and /profile routes on the demo: https://nomrah.dev/

@harmon25 Thanks for all the answers! Trying this out on a sideproject and ran into a few issues (mainly with console.log but saw your comment thread on the original repo and that worked a charm) - just wondering if you have any ideas on using WebSockets in the app? Since WebSocket is only included in the client side I am getting an Cannot read property 'WebSocket' of undefined which makes sense. I’m not so familiar with the hyrdation flow, but would it be trivial to say delay the execution of the webhook logic until the client side hydration begins?

I actually just responded in the original repo with some thoughts on that: https://github.com/revelrylabs/elixir_react_render/issues/44

Here is an example of how i was able to get the node ssr process to connect back to the phoenix socket.
It only required an isomorphic WebSocket in the right place for phoenix to know how to instantiate the phoenix channel socket.

In looking closer at the phoenix source it looks like WebSocket could be passed into the phoenix socket constructor as the transport option - rather than hacking global like i did :stuck_out_tongue:

To more directly answer your question:
I think a conscious decision should be made on what data you want to have available for the SSR, and thus rendered on the server vs the data that will be fetched async once the client bootstraps.

I would also separate out the phoenix socket/channel logic from the content being server rendered via some indirection through redux or something similar. This would allow you to not include that client code when you SSR, and maybe just hydrate redux state or something on the server, and not have to worry about how it got there.

In your app.js file is where you have the socket code, and maybe add extra redux middleware to manage channel data when running in the browser vs on the the static server render.

Can’t seem to edit my post, but I did find a solution. The above bug occurs because it the phoenix.js library attempts to call location which I am assuming is not defined in the node runtime and is browser specific.

You can replicate this @harmon25 by adding a test channel in your app, and then importing the socket.js file in your index page.

The solution, is quite simple, but basically you need to change:

const socket = new Socket("/socket", {});
let socket = new Socket("ws://localhost:4000/socket", {});

Which then bypasses the location calls and the app will compile correctly.

My only concern is that this will be an ongoing theme where the react app expects there to be a browser runtime present (e.g. through libraries etc.) and cause these issues.

Started writing this in the AM, and then got busy with work…

If you are not using redux or some global state thing, the right approach would be a context in the ClientContainer.

Here is a gist of what that might look like.

Not tested, but should work.

You could combine the above with a ChannelContext that was responsible for managing and communicating the channel data/status to child components.

This should be imported on the client container, or maybe app.js, but not in components down the tree that end up on the server render. Which is the problem I think you are having… this websocket stuff will not work in the context of node without some help - like the channel logger I linked above.

It can be done for sure - but if you have an authenticated socket, that authentication would need to be considered when connected back to localhost from the server render node process…

Take a look at the kinda hoops are required for hydrating state in apollo for example:

As you point out, this is a bit challenging if you are doing stuff with sockets all over your tree…this is why i suggest very carefully locating the socket related code - and you probably will need to use something like redux to hook it up with react properly - or else you will have re-renders all over the place…