webpacker-react icon indicating copy to clipboard operation
webpacker-react copied to clipboard

Server-side rendering

Open renchap opened this issue 8 years ago • 45 comments

Using ExecJS, we should be able (optionally) to run React on the server, render the content, and then re-hydrate the components in the browser.

The steps for this would be something like:

  • find how to determine the path of the pack file containing the component. This is not trivial and I think we will need to load all the packs, maybe with an option for the user to specify the correct one and avoid loading all of them
  • get the packfile content. Location may vary between development (in public/packs/) and production (need to use the Webpacker manifest)
  • create a new ServerSideRendering based on ExecJS, with a render(component, props) option
  • change render_component to call the server-side renderer if configured to do so
  • change the webpacker-react JS module to now create a new component, but hydrate an existing one
  • add various improvements, as console replay in the browser, error handling, ...

renchap avatar Jan 31 '17 09:01 renchap

I'm gonna start working on this soon. I think to load the pack files we should stick to some convention (probably configurable) like react-rails' components.js does, so we know where can find the components.

daninfpj avatar Feb 07 '17 23:02 daninfpj

You can have multiple components registered in one packfile. The logic is to have separate packs for different parts of your website, but you may have multiple root components in it, especially if not using an SPA.

renchap avatar Feb 08 '17 20:02 renchap

Exactly, that file (components.js) loads all the components you'll be using for sever-side rendering and their dependencies. I think that makes sense and simplifies the logic of trying to locate the right packfile. And in practice you don't necessarily have to include it in your views, you can include there the separate packs you mention.

daninfpj avatar Feb 08 '17 22:02 daninfpj

Seems good. Maybe use a more expressive default name, like app/javascripts/components/serverRendering.js? Also this should be a configuration option, and allow an array of files to be loaded.

renchap avatar Feb 10 '17 09:02 renchap

I'd try to challenge the whole server-side rendering idea later :)

sevos avatar Feb 26 '17 15:02 sevos

Hi guys, any news about server side rendering?

risinglf avatar Apr 09 '17 20:04 risinglf

I ~~hate~~ have mixed feelings with the idea of server side rendering:

  1. it kills the purpose of SPAs - didn't we want to have single page apps because we wanted to take the load off the server and move to the client/browser to allow scalabilityTM? And now what? Again moving back rendering to server. It seems like a roundtrip for me. Let's just use Turbolinks and call the SPA experiment a failure ;)

  2. it surely adds some edge-cases (not everything can be handled outside of the browser, some libs?).

sevos avatar Apr 09 '17 20:04 sevos

@sevos for our company server side rendering is not a choice: it's mandatory. We don't have a full SPA but many simple widgets that for SEO and UX reasons need to be already present when the browser/bot loads the page.

It can of course add some edge cases due to many different libraries available, but IMHO is up you to choose one that does not need the browser env or to implement a different approach when the server side rendering is running...

What do you think?

risinglf avatar Apr 25 '17 13:04 risinglf

@risinglf @sevos @daninfpj Why would prefer to put server rendering in this library compared to using React on Rails?

justin808 avatar Apr 25 '17 21:04 justin808

@justin808: The whole point of this library (not just server rendering) is to take advantage of Rails 5.1 (currently on rc2) first-class support for Webpack and React. This allows for a cleaner and simpler integration in my opinion.

daninfpj avatar Apr 26 '17 15:04 daninfpj

@daninfpj wrote:

@justin808: The whole point of this library (not just server rendering) is to take advantage of Rails 5.1 (currently on rc2) first-class support for Webpack and React. This allows for a cleaner and simpler integration in my opinion.

We're almost done with that.

See https://github.com/shakacode/react_on_rails/pull/822 and https://github.com/shakacode/react_on_rails/pull/811.

and see:

https://github.com/shakacode/webpacker_lite

justin808 avatar Apr 26 '17 22:04 justin808

React on Rails 8.0.0 shipped with support for webpacker_lite. I think this has the server rendering support you desire.

justin808 avatar May 30 '17 09:05 justin808

Hi guys, how is this feature progress?

risinglf avatar Sep 25 '17 10:09 risinglf

Is there anything we can do to help?

caselas avatar Sep 25 '17 18:09 caselas

I have not got time to really work on server-rendering. If you would like to have a stab at it, feel free! I outlined my ideas in this issue and I am available to discuss it further. React 16 changes server-side rendering and hydrating, and I think it would be great to support it.

I am not opposed to a minimal and non-modular approach at first, supporting only mini_racer (via ExecJS?) to keep things simple.

renchap avatar Sep 26 '17 14:09 renchap

I've got a minimal approach working already if that is of interest. I'm using webpacker-react for the client side stuff but the server side is independent. It's pretty heavily influenced by react-rails as I thought their implementation was pretty clean.

I took the single point of entry approach with one "server" pack that imports all the various components. Right now the issue I'm working through is due to the way webpacker 3 enables the style-loader when HMR is enabled, you can't server side render and use HMR at the same time.

wingrunr21 avatar Sep 27 '17 22:09 wingrunr21

Nice! This seems like a good start. There is a related issue in the Webpacker repo: https://github.com/rails/webpacker/issues/842

What is the issue with style-loader and server-rendering?

renchap avatar Sep 27 '17 23:09 renchap

Ya, I've read through that issue. I actually don't need to turn off inline mode like is specified there. react-rails removes the client require that inline mode inserts. Simply adding a var self = self || this fixes the other error.

I'm messing with a server-only webpack config to try and figure out a clean way of having HMR and server side rendering. I'm not a huge fan of that thread's suggestion of maintaining a per-file list. IMO there should be another point of entry for server-side packs that the second config handles.

Another issue at play here is whether or not the client will then mount the same component on top of the server side render. This may not be wanted if you are basically statically rendering React components out, but if you are trying to bootstrap a SPA you probably want to do that. One thing at a time though 😄

What is the issue with style-loader and server-rendering?

style-loader basically wants a DOM to write <style></style> out to. It is actually recommended to use extract-text-plugin for server side, but we don't have control over webpacker enabling that right now. If you turn off HMR, webpacker disables the style-loader so everything works fine.

wingrunr21 avatar Sep 27 '17 23:09 wingrunr21

Well, server rendering should not worry about styles at all and not output anything related (except for css modules). Hydrating a server-rendered component is a mandatory feature, I dont see a usecase for server-side only React (just use Ruby!).

React 16 changed quite a lot of things related to SsR’ I foudn this article useful: https://medium.com/@aickin/whats-new-with-server-side-rendering-in-react-16-9b0d78585d67 I dont know if it may help you.

Can you publish your work in progress somewhere so I can have a look and we can discuss over real code? This will be easier 🙂

renchap avatar Sep 27 '17 23:09 renchap

Agree to disagree on that one. We use styled-components pretty extensively. As they ship all styles via JS and subsequently write a style tag to the DOM on component tree render, you have to have a way of pulling the styles out of the render tree on the server. Luckily, they added first party support for server side rendering in v2, so that works pretty nicely.

Also, not everyone's whole stack is Ruby. We've got SPA, express, and Rails apps in production. We have a React-based component library that is shared between them. Components like our footer take an initial set of props and are rendered out. Having that statically rendered is a valid use case for us.

The issue isn't even about the appropriateness of CSS on the server. When the style-loader is enabled webpack emits additional code that causes execJS to choke. However, you need the style-loader for HMR. So, you either get HMR or server side rendering.

Sure, I can put stuff up in a gist

wingrunr21 avatar Sep 28 '17 00:09 wingrunr21

Ok I see how it works. Let me correct what I said by "server-rendering should ignore style-loader and other CSS-related webpack loaders :) For production your stylesheets will be compiled in CSS files by extract-text-plugin, and in dev your styles are inserted into a <style> tag by style-loader.

I guess we will need hooks to allow styled-components (and other similar projects) to work with webpacker-react server-rendering, as it it a library-specific feature.

The first and simplest goal of server-side rendering is to be able to call renderToString() on your component on the server and output the result into your Rails view, and then to call ReactDOM.hydrate when mounting the component client-side.

renchap avatar Sep 28 '17 11:09 renchap

Here's a gist of what we are using right now. Like I said, this is a pretty simple implementation. I'm still wondering if a server-only webpack config is a better approach. It would make messing around with CommonsChunk and such easier and would enable hmr to work alongside.

https://gist.github.com/wingrunr21/b2e2a1aca3083eb877a6deae9dedbd89

wingrunr21 avatar Oct 02 '17 19:10 wingrunr21

@renchap @wingrunr21 I am about to tackle this in my app. Is there any progress on this or should I pickup from the gist @wingrunr21 posted?

tomasc avatar Oct 25 '17 09:10 tomasc

@tomasc I won't be submitting a PR to this repo for server side render support. As we continued to iterate on our solution, it became obvious that a more standalone solution was the best fit. We are prepping to open source a generic SSR solution for webpacker (which will still have full support for webpacker-react).

wingrunr21 avatar Oct 25 '17 14:10 wingrunr21

@wingrunr21 that sounds good. If interested, I can help test your project as I am about to start dealing with SSR and solution with bare-bones webpacker would work best in my case.

tomasc avatar Oct 25 '17 14:10 tomasc

@tomasc I havent got time to work on this yet. Feel free to tackle it if you want! The preferred way is to open a PR as soon as you have a WIP, so we can discuss about it while it progresses.

@wingrunr21 I am curious about how you want to tackle it at the Webpacker level. Can you outline how it would work?

renchap avatar Oct 25 '17 18:10 renchap

@wingrunr21 Yep that sounds interesting. Just commenting so I watch this thread (spying from react-rails 😉 )

BookOfGreg avatar Oct 25 '17 18:10 BookOfGreg

@BookOfGreg honestly, your SSR support formed a really solid basis for the work. It definitely gave us a good starting point on a solution that was known to work. Your project is also a reason we wanted to target a more generic webpacker solution. It's fairly difficult to parse out how to implement SSR in an express application without using something like create-react-app or next.js. We didn't think another implementation of SSR (with react-rails and react_on_rails both having implementations) would benefit from being specific to a given project.

@renchap Sure. We wanted this to work as closely as possible to how "real" SSR is done in a node environment. As I outlined before, our onus around this is because our React codebases are used across various environments. In addition, the vast majority of SSR testing by upstream users is done against a node environment. Emulating how those setups work seemed to be the optimal approach.

  1. We are using a separate server config. We separated out the SSR entry point and the rest of the packs so that client side JS can still be run through things like CommonsChunkPlugin. The server config is derived from the webpacker config as much as possible We will probably push a few PRs against webpacker to aid with extracting that config (for instance, their ExtractTextPlugin configuration is not exported at all when the dev server is running and HMR is enabled)
  2. We implemented adapters to support rendering the JS to the client that is inline with how client side libraries will then mount the components. You can see that in action on www.guildeducation.com. The navigation and footer are SSR components that are remounted via webpacker-react's auto mounting
  3. We are currently working through more complex requirements such as using a StaticRouter from react-router or dealing with redux stores. The good part is that sticking fairly close to how node does things means these use cases can be supported in much the same way

wingrunr21 avatar Oct 25 '17 19:10 wingrunr21

@wingrunr21 is there any updated on this please? I am eager to test or help out.

tomasc avatar Nov 02 '17 19:11 tomasc

@tomasc sorry, our November/December ended up being crazy with other work. I'm working on it this weekend and hope to have some good stuff come next week.

wingrunr21 avatar Jan 13 '18 14:01 wingrunr21