server-render

v1.0.0-alpha300.11Published 8 months ago

server-render

Source code of released version | Source code of development version


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

Usage

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

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

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

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

The sink object may also expose additional properties depending on the environment. For example, on the server, sink.request provides access to the current request object, and sink.arch identifies the target architecture of the pending HTTP response (e.g. "web.browser").

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

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

Likewise on the client:

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

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

Note also that the client example does not end up calling any methods of the sink object, because ReactDOM.hydrate has its own similar API. In fact, 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:

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

In this example, the callback not only renders the <App /> element into the 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 is designed to be generically useful for any kind of server-side rendering.

React 16 renderToNodeStream

Since React 16, it is possible to render a React app to a node stream which can be piped to the response. This can decrease time to first byte, and improve performance of server rendered apps.

Here is a basic example of using streams:

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