Understanding the server-render package


#1

All, thanks for your help. I’m looking at the documentation for server-render at https://docs.meteor.com/packages/server-render.html and I just feel like I’m coming up empty. For example:

import React from "react";
import { renderToString } from "react-dom/server";
import { onPageLoad } from "meteor/server-render";

import App from "/imports/Server.js";

onPageLoad(sink => {
  sink.renderIntoElementById("app", renderToString(
    <App location={sink.request.url} />
  ));
});

It shows us importing App from /imports/Server.js but what does that do? Is that a router? If not, what is it? We can see this on the client example too:

import React from "react";
import ReactDOM from "react-dom";
import { onPageLoad } from "meteor/server-render";

onPageLoad(async sink => {
  const App = (await import("/imports/Client.js")).default;
  ReactDOM.hydrate(
    <App />,
    document.getElementById("app")
  );
});

What is imports/Client.js ? Is that a router? Is it a container? What is it? What is it for?

Sorry for my frustration but because there’s no mention of routing and no mention of what those files do, I’m very confused.


#2

Yes, they are router. For example if you use react router, those files should look like:

import React from 'react';
import { Route, Switch } from 'react-router-dom';

import SomeComponent from '/path_to_component.js';
import OtherComponent from '/path_to_other_component.js';

export default (
  <Switch>
    <Route path="/" component={SomeComponent} />
    <Route path="/other" component={OtherComponent} />
  </Switch>
);

the client.js and server.js may be the same file. But normally, server.js has only some routes which you want to support server render. For example the route /admin you don’t need to make it to support server render.


#3

A good example is https://github.com/jbaxleyiii/ReactNYC-SSR. That one really helped me understand how this package works.


#4

But how do either of those render the react module as a string?


#5

the renderToString method executes the code with a fake server-side DOM and converts it to html, which is sent to the client on first load.

Then on the client, the hydrate method tries to keep as much of the DOM intact as it runs the app and swaps out the static html with live React components


#6

OK, and before I start thanks so much for responding, but I’m not seeing how the server actually does that. So here’s my code without a router:

/server/server.js

import React from "react";
import { onPageLoad } from "meteor/server-render";
import { renderToString } from "react-dom/server";

import App from "/imports/ui/App.js";

onPageLoad(sink => {
  const html = renderToString( <App /> );
  sink.renderIntoElementById("app", html);
});

Which is almost a perfect copy and paste from the docs. However, my “App” is not a router, it’s just a stateless component:

/imports/ui/App.js

import React, { Component } from 'react';

export default class App extends Component {

  render () {
    return (
      <div className="app-container">
        <header>Meteor SSR Blog</header>
      </div>
    );
  }

}

In this situation, there should be a component mounted to #app but there isn’t. So where does that trickle from server to client? If I put a router in Imports, all it can do is do client side routing… so what am I missing here?


#7

I guess I’m simply asking what connects server to client here? It doesn’t look like anything is pointing to anything else.


#8

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


#11

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.


#12

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.


#14

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!


#15

Did you figure out routing? @thatgibbyguy


#16

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.


#17

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


#18

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


#19

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)

#20

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.


#21

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?


#22

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.


#23

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