Understanding the server-render package

I’m not really sure what you mean
When the user initially loads the page the client sends a HTTP request to the server which returns a plain HTTP response

I’ll try to explain by going step by step:

  • The user navigates to the website
  • The client sends a request for a URL to the server
  • The server generates static HTML and sends it to the client
  • The client receives static html which the user can see instantly
  • React starts up on the client and loads the components into memory
  • react-dom compares the app in memory to the static html and attaches the behaviour and event listeners it needs onto the static DOM to make it into a dynamic react app (This process is called Hydration)
  • If a client side router is used, further navigation is handled on the client side only

The benefit of SSR is that the user can see the page as soon as the html arrives, rather than after all the javacsript has loaded / compiled / run and generated the page client side

There is no real connection between the two

1 Like

I withdrew my other two comments, because I think now I’ve got some better questions. First I’d like to just point out that I’ve done this without meteor before just using express, I’m just trying to figure out how it works on meteor. You have two points which are what I’m asking about:

  1. Client sends a request for a URL to the server.
  2. Client receives static html which the user can see instantly.

I understand that, what I don’t understand is that while following the meteor docs to a t, that is not happening. To me that is not happening because there needs to be something on the client that does your second to last bullet “react-dom compares the app in memory to the static html and attaches the behaviour and event listeners it needs onto the static DOM to make it into a dynamic react app (This process is called Hydration)”

Without that “hydration” the html should still have been served to the client a la the meteor docs. That is not happening. So the question is - where does the hydration have to occur. Does it happen in the router? If so does the router live in shared code? If no, then it happens on the client - how does the client know based on which route what component should react compare the html string to the react module?

I know these are nitpick and I apologize, but I’ve exhausted the documentation and don’t know where else to turn.

How are you creating the HTML that contains the container element with id="app"? Server rendering won’t work unless it’s rendering into static HTML, so Blaze templates (which create HTML elements dynamically on the client) are not sufficient, because the DOM nodes they eventually create (on the client) are not available to the server rendering code.

This is why the ReactNYC app uses the static-html package instead of blaze-html-templates in its .meteor/packages file.

1 Like

Nevermind, so I found a github issue from 2015 about this. I see exactly what you were saying.

$ meteor remove blaze-html-templates
$ meteor add static-html

OK, so ssr is actually working now. I just need to figure out the routing. Thanks y’all!

1 Like

Did you figure out routing? @thatgibbyguy

Nope, sure didn’t. So I could render from the server initially but could not route from the serve after. Very disappointed in the lack of documentation for this.

Hmmmm. I let you know if I figure it out.

Please do. You can follow along with what I did here

routing is no problem with react-router. This is a simple example (with addition of styled-components and react-helmet):

import { renderToString } from 'react-dom/server'
import { onPageLoad } from 'meteor/server-render'
import { ServerStyleSheet } from 'styled-components'
import Helmet from 'react-helmet'
import React from 'react'
import App from '/imports/ui/App'
import { StaticRouter } from 'react-router-dom'

export const render = async sink => {
  const context = {}

  const WrappedApp = (
    <StaticRouter location={sink.request.url} context={context}>
      <App />
    </StaticRouter>
  )

  const sheet = new ServerStyleSheet()
  try {
    const html = renderToString(sheet.collectStyles(WrappedApp))
    const helmet = Helmet.renderStatic()
    sink.appendToHead(helmet.title.toString())
    sink.appendToHead(sheet.getStyleTags())
  
    sink.renderIntoElementById('app', html)
  } catch (e) {
    /* eslint no-console:0 */
    console.error(e)
  }
}

// Handle SSR
onPageLoad(render)

As far as I understand, that would not be reactive. I do not see withTracker in there, which is exactly what we’d need to make that reactive.

Also, that is not showing children routes. In that example we’d have to render and entire new app layout for each route.

As I said, I can get to that point fine, it’s doing actual routing that I cannot find documentation for or examples that utilize withTracker.

this is SSR. SSR is only for the first page request of a user. If the page is loaded, the page changes happen on the client. The SSR code has nothing to do with reactivity.

There is no relation to withTracker.

Also, that is not showing children routes. In that example we’d have to render and entire new app layout for each route.

the example renders the full app html for the path that is given by the server request. It will render whatever route it needs to render:

 <StaticRouter location={sink.request.url} > ...

As I said, I can get to that point fine, it’s doing actual routing that I cannot find documentation for or examples that utilize withTracker .

I don’t understand that statement. Do you have an issue with SSR or with client-side routing? And what has withTracker to do with it?

1 Like

I’m sorry, but it feels like you’re arguing to win instead of to help. First, I provided you a link to what I was working on, in that link I walk through step by step what I’m doing - you have not referenced any of that.

Second, what you are showing for sink.request.url I already know and have shown to know that in what I provided. What I do not know is: how to show different container components or different views entirely by route from the server. I cannot be more clear than that. Your example fundamentally does not show that or prove that it’s possible.

Finally - there absolutely is a relation to withTracker because according to meteor’s own documentation, withTracker

allows you to create a container component which provides data to your presentational components.

So in SSR rendering, you would either have to have no reactivity as you show in your example, or you have no way of routing from the server with reactive data.

Of course, I am more than happy to be wrong, but you are not showing where or how I am wrong.

Ok i checked the link above and i looked through your tutorial there, but I don’t think that this is useful to describe your problem. You have not done the routing section yet, so i guess, you want to add that section (?).

Anyway. You have to understand first how to use react-router on the client. Make your app is working with routing, but without SSR first and show us some actual code.

Also, SSR couldn’t care less if you try to render simple components or complex data-fetching containers. That’s why i say withTracker is irrelevant.

Your example fundamentally does not show that or prove that it’s possible.

Sorry, but my example above exactly shows that: Understanding the server-render package

This is how you have to change the server part in your tutorial: https://johngibby.com/blog/Server_Side_Rendering_With_Meteor_and_React_-_Part_Two

So in SSR rendering, you would either have to have no reactivity as you show in your example, or you have no way of routing from the server with reactive data.

I don’t understand that statement.

Remember, SSR only happens on first page load. This is not reactive, its a request, client gets a response (the full rendered html), with whatever data you load in your components. If one of your component’s happen to load data from meteor collections using withTracker, it will load directly from the database without beeing reactive.

That beeing sad, you will run into some challenges. E.g. you will notice:

  • the server response might render components using more data than the client. This is because the server does not use publicatoins
  • after hydration, the client will suddenly have no data anymore. This is because collections don’t have a “hydration” concept, they get filled by subscriptions. For this you will need https://atmospherejs.com/?q=staringatlights:fast-render

OK, take this scenario:

app.com -> app.com/messages

User hits / and then routes to /messages - this all happens on the client. No problem, initial route is loaded by server, client side routing takes control after that.

app.com/messages -> app.com

User hits /messages and then routes to /. Now we have our first problem - messages was rendered by the server. This is where I’m lost - there is nothing about your code or in the meteor docs that says how to route to /messages from the server - and certainly nothing about using a different container component for that route based on what sink.request.url returns.

The next layer of my question is /messages has to show reactive data as the messages come in. If the initial container component is what provides the initial data, once the DOM is hydrated the client side stuff will take over. That data needs to be reactive though, and meteor requires withTracker to make it reactive.

So… that means the container component has to be wrapped with withTracker which basically is breaking routing.

I am super appreciative of your time and help here, it’s informing a big business decision right now over what to use for our next application.

on app.com/messages sink.request.url will app.com/messages as well.

so <StaticRouter location={sink.request.url}> will make ReactRouter render every <Route /> that matchs that location. (and also every data-fetching-component that are wrapped in these routes).

So when a user hits app.com/messages (means as initial route), the server will render the app as it would be in that route.

The next layer of my question is /messages has to show reactive data as the messages come in. If the initial container component is what provides the initial data, once the DOM is hydrated the client side stuff will take over. That data needs to be reactive though, and meteor requires withTracker to make it reactive.

hydrating means that the client code will run and react will re-use the markup. Apart from that, everything else will be like there would be no server rendering. Subscriptions will start, collections will fill with data and withTracker-containers will update if new messages arrive.

So… that means the container component has to be wrapped with withTracker which basically is breaking routing.

The router does not care what your components do. It just mounts the components that match your route.

The important question is now: have you tried it out? what does not work like you expected?

I am super appreciative of your time and help here

you’re welcome. and sorry to sound harsh, the language barrier and limited time makes it a bit harder to find the right words.

it’s informing a big business decision right now over what to use for our next application.

One thing to consider: SSR with meteor’s older pub/sub-mechanism works, but it’s not so common anymore and finding tutorials, help and tools is becoming harder. Pub/sub got displaced by graphql (apollo) and you will find much more resources for that.

3 Likes

This thread really proves the need for the Meteor guide to have a Server Side Rendering section for React apps

If anyone has some spare time to summarise the info in this thread, it would be very much appreciated: https://github.com/meteor/guide/blob/master/content/react.md

4 Likes

If it’s helpful, I have a starter here which does SSR:

4 Likes

Thank you @macrozone for your patience.

@coagmano agreed. you have parts of the documentation dealing with making react components reactive which make you think everything has to be wrapped in withTracker and then you go to SSR portion of react and you get … a couple paragraphs.

I’d love to update that, but I still need to figure it out haha.

@macrozone @captainn
I have made my setup just as the above code you have thankfully shared. There was a lot of errors mostly regarding CSS, but I fixed them all, the app seems to work again.

However, the SSR isn’t working. I’m not getting html from the server but an empty div. It works just as before rendering in the client. What may I be doing wrong or what’s missing?

Also, what happens to the Meteor.startup in functions both server and client ? Do we completely skip them?

Thanks!