Open ZhangYiJiang opened 6 years ago
Progress on this issue can be tracked on the we-ssr-now branch. It's pretty interesting all of the technical issues I've faced so far
We used Webpack to rewrite imports, which means we can't just use babel-node
to run the SSR server in development. Webpack hot reload is possible in theory, but I'd rather stab myself in the eye then spend another minute writing Webpack config, which leaves us with waiting for a full build to get runnable code. Fortunately Jest is configured to rewrite routes, so now I've written some unit tests and am using those instead to fix bugs
The DOM cannot be touched in React now. At all. Every window
and navigator
used should be looked over carefully. Also Portals and ErrorBoundary are completely unsupported in SSR. Oh, and SSR is completely synchronous, so any setTimeout or other async operation will fail miserably.
The HTML template is surprisingly painful. We want to use the output from Webpack HTML plugin, since that has all of the <script>
and <link>
tags which link to our compiled assets, but we also need to inject our SSR rendered HTML. Also, React Helmet works in the browser via direct DOM manipulation, which obviously does not work here, so instead it just outputs a bunch of strings and expects us to put it into the correct place in the template.
I think what we can do is to use Webpack HTML plugin to create a template, then inject default values to create the static index.html and load that template in the SSR server to inject the Helmet HTML snippets
404 pages renders correctly, but to actually return the correct status code (as well as for 302 redirects) we need to use the context
prop passed down through React Router, with each component connected to the router poking at the object
Data fetching is problematic as expected. React Router suggests static routing, but that does mean routing code is going to be duplicated on both client and server
Redux Persist has side effect when importing storage. This was the same issue we ran into the last time. Setting up a flag to only initialize storage on SSR isn't enough because the reducers are initialized on import, and the env var isn't set in the tests in time, so I just mocked it again
Also, on an architecture level I believe implementing SSR will cause the app to be more monolithic. This is because the data layer can now be shared between server and client. Imagine if we have a GraphQL store - the SSR server will then just query it locally to fetch the data. This is pretty neat if you think about it.
Aside from the DOM and async stuff, the rest of the issues can be solved with webpack on the server side.
Long ago I actually had an entire SSR code base that I learned over at Carousell, it was intended to be a learning thing so it was private. Just made it public: react-prod-learn. Ultimately I didn't go with SSR because of the problems you raised and we don't actually see much benefit aside from performance (which comes with a pretty heavy cost).
The previous comment is outdated. I've fixed/hacked around most of these issues, and have found some new ones
${ }
is used by Webpack HTML plugin, while Mustache {{ }}
is used by the SSR https://github.com/nusmodifications/nusmods/blob/we-ssr-now/www/src/index.html. To make sure the Mustache templates don't leak into the HTML served by Webpack Dev Server, I created a tiny Webpack plugin that strips Mustache templates only in development https://github.com/nusmodifications/nusmods/blob/we-ssr-now/www/webpack/webpack.parts.js#L326window.REDUX_STATE
and is passed into the Redux store as initial state https://github.com/nusmodifications/nusmods/blob/we-ssr-now/www/src/js/main.js#L20
I believe it should be possible to make NUSMods SSR without too much pain. The main benefit of this is that the static like FAQ and semi-static pages like the module/venue pages should load much faster, and SEO on those pages will also be much better. The additional server load will be mitigated by adding a caching layer in front of the renderer.
The key steps are to making SSR work are (reference: https://medium.freecodecamp.org/demystifying-reacts-server-side-render-de335d408fe4)
componentDidMount
. Sample code: https://gist.github.com/fdecampredon/037522907cc79183feef83595b9892ae<AppShell>
with a server side version of the Redux store and React router. This is relatively simple - the code used in the article above is actually pretty close to what we might do.Open questions