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!