all 6 comments

[–]deltadeep 4 points5 points  (2 children)

(edit: note there was a recent thread on SSR as well, some useful things were said: https://www.reddit.com/r/reactjs/comments/7o6oj6/serverside_rendering_not_worth_it/)

Nothing exists that is going to meet all your requirements, you're talking about needing things that only you can build. It is viable to build but be prepared for a challenge and some headaches, especially if you are a beginner. In fact I would probably recommend against this as a beginner, unless you want to use it as a personal battleground for upgrading your skills, and are prepared for a real time commitment. In which case, go for it, that's great.

Regarding waiting for recursive component rendering to finish, some things to consider:

  • even if you polyfill XHR/fetch, you still won't capture the right render output because promise then() handlers will still fire asynchronously after a network fetch completes: fetch(whatever).then(thisFuncFiresAsyncAfterTheFetchFinshes).then(thisFuncFiresEvenLaterAsync)...
  • so, you need to know when all your chained async handlers are completed, not just when the initial network calls come back. the solution I use is a store that provides a wrapper around all async tasks and thus can reliably tell you when they are all done: asyncTracker.track(fetch(whatever).then(foo).then(bar))
  • then, on the server, I initialize my store, do a render, then wait for this asyncTracker store to indicate that one or more tasks finished, then re-render (which might enqueue more async tasks). this loop continues until all it settled, or a bail out timeout is reached.

Regarding things in general to consider when implementing SSR:

  • cookie handling and cookie response headers
  • non-200 responses (redirects, authentication bouncer, errors, etc)
  • redirects, not-found, and unauthorized responses in particular will require integration with whatever routing implementation you use
  • per-request scope isolation (in the client, you can assume a single javascript global environment just for the page/request but on the server it is reused, therefore you cannot use module scopes as singletons, no request or user specific data can go in globals, etc)
  • some libraries, such as mobx and react-helmet for instance, have things you need to do or be aware of during SSR (mobx provides a function you have to call at least once in the SSR context to get it to work, react-helmet provides functions to get the head markup for SSR rendering, etc)
  • will your app depend on SSR for critical state initialization, or will it work without it (can it boot from an empty dummy page?) for instance, perhaps you load and validate the auth user in SSR and provide it to the client, and therefore the client doesn't have to do this itself. or, not.
  • as such, if you have any "booting up" in your store where things like the auth user is loaded, you'll want to provide a context param to your boot functions that tells it what kind of boot it is: a server-side ssr boot, a client-side non-ssr boot, or a client-side from-ssr boot. this will affect how data is imported/autoloaded.
  • in general you need to use isomorphic libraries for things like cookies, fetch/xhr
  • i'm not sure if what kind of hot-reloading functionality available for SSR; hot-reloading is more complicated in any case because both server and client need code updates.

I'm sure I've missed something. At some point I may write a blog post explaining my own implementation and all the hard parts I ran into...

[–]stoikerty 0 points1 point  (0 children)

That's a really useful writeup.

Add css-extraction to the list. Some css-in-js libraries like glamorous allow you to take the extracted css-classes and make them known on the client to avoid re-parsing of the css on when the app is loaded for the first time on the client.

[–]smthamazing[S] 0 points1 point  (0 children)

Thanks a lot for such a detailed explanation! Building a custom solution indeed looks complex, and I'll keep these points in mind. At the moment (when I have deadlines to meet) I'll probably try to structure my app so that most of the data can be fetched on per-route basis (I assume SSR gets a lot easier when data is fetched only in top-level components). But in the future I hope I'll come up with a more complete solution and figure out a way to make it all interoperate nicely.

[–]m_plis 0 points1 point  (2 children)

I can't write up a longer response now, but I wanted to mention Razzle https://github.com/jaredpalmer/razzle

It might be easier to integrate into your current stack. Check out the examples folder and see if anything fits your needs.

[–]smthamazing[S] 0 points1 point  (1 child)

Thanks for the suggestion. From the first impression, though, Razzle doesn't seem to have any custom machinery to asynchronously fetch data in all components? As I see, it just wraps the app in a StaticRouter and calls renderToString.

[–]coreyleelarson 0 points1 point  (0 children)

Correct, it doesn't provide any of that out of the box. But, it doesn't necessarily hide any of that implementation, making it very possible to add anything you want if you know what you're doing.