server-render

Documentation of Meteor's server-render package.

This package implements generic support for server-side rendering inMeteor apps, by providing a mechanism for injecting fragments of HTML intothe <head> and/or <body> of the application’s initial HTML response.

Usage

This package exports a function named onPageLoad which takes a callbackfunction that will be called at page load (on the client) or whenever anew request happens (on the server).

The callback receives a sink object, which is an instance of eitherClientSink or ServerSink depending on the environment. Both types ofsink have the same methods, though the server version accepts only HTMLstrings as content, whereas the client version also accepts DOM nodes.

The current interface of {Client,Server}Sink objects is as follows:

  1. class Sink {
  2. // Appends content to the <head>.
  3. appendToHead(content)
  4. // Appends content to the <body>.
  5. appendToBody(content)
  6. // Appends content to the identified element.
  7. appendToElementById(id, content)
  8. // Replaces the content of the identified element.
  9. renderIntoElementById(id, content)
  10. // Redirects request to new location.
  11. redirect(location, code)
  12. // server only methods
  13. // sets the status code of the response.
  14. setStatusCode(code)
  15. // sets a header of the response.
  16. setHeader(key, value)
  17. // gets request headers
  18. getHeaders()
  19. // gets request cookies
  20. getCookies()
  21. }

The sink object may also expose additional properties depending on theenvironment. For example, on the server, sink.request provides access tothe current request object, and sink.arch identifies the targetarchitecture of the pending HTTP response (e.g. “web.browser”).

Here is a basic example of onPageLoad usage on the server:

  1. import React from "react";
  2. import { renderToString } from "react-dom/server";
  3. import { onPageLoad } from "meteor/server-render";
  4. import App from "/imports/Server.js";
  5. onPageLoad(sink => {
  6. sink.renderIntoElementById("app", renderToString(
  7. <App location={sink.request.url} />
  8. ));
  9. });

Likewise on the client:

  1. import React from "react";
  2. import ReactDOM from "react-dom";
  3. import { onPageLoad } from "meteor/server-render";
  4. onPageLoad(async sink => {
  5. const App = (await import("/imports/Client.js")).default;
  6. ReactDOM.hydrate(
  7. <App />,
  8. document.getElementById("app")
  9. );
  10. });

Note that the onPageLoad callback function is allowed to return aPromise if it needs to do any asynchronous work, and thus may beimplemented by an async function (as in the client case above).

Note also that the client example does not end up calling any methods ofthe sink object, because ReactDOM.hydrate has its own similar API. Infact, you are not even required to use the onPageLoad API on the client,if you have your own ideas about how the client should do its rendering.

Here is a more complicated example of onPageLoad usage on the server,involving the styled-components npm package:

  1. import React from "react";
  2. import { onPageLoad } from "meteor/server-render";
  3. import { renderToString } from "react-dom/server";
  4. import { ServerStyleSheet } from "styled-components"
  5. import App from "/imports/Server";
  6. onPageLoad(sink => {
  7. const sheet = new ServerStyleSheet();
  8. const html = renderToString(sheet.collectStyles(
  9. <App location={sink.request.url} />
  10. ));
  11. sink.renderIntoElementById("app", html);
  12. sink.appendToHead(sheet.getStyleTags());
  13. });

In this example, the callback not only renders the <App /> element intothe element with id="app", but also appends any <style> tag(s)generated during rendering to the <head> of the response document.

Although these examples have all involved React, the onPageLoad API isdesigned to be generically useful for any kind of server-side rendering.

Streaming HTML

React 16 introduced renderToNodeStream, which enables the reading of rendered HTML in chunks. This reduces the TTFB (time to first byte).

Here is a renderToNodeStream example using styled-components. Note the use of sheet.interleaveWithNodeStream instead of sink.appendToHead(sheet.getStyleTags());:

  1. import React from "react";
  2. import { onPageLoad } from "meteor/server-render";
  3. import { renderToNodeStream } from "react-dom/server";
  4. import { ServerStyleSheet } from "styled-components"
  5. import App from "/imports/Server";
  6. onPageLoad(sink => {
  7. const sheet = new ServerStyleSheet();
  8. const appJSX = sheet.collectStyles(
  9. <App location={sink.request.url} />
  10. );
  11. const htmlStream = sheet.interleaveWithNodeStream(
  12. renderToNodeStream(appJSX)
  13. );
  14. sink.renderIntoElementById("app", htmlStream);
  15. });