Server-side React

⚛️

@charlespeters

Server-side rendering your React application means leveraging a server to send an initial render to your client.

Server-side rendering your React application means not sending your user an empty <div>, a single bundle
and a 🙏

  • Universal
  • Isomorphic
  • Environmental
  • Client-server

Isomorphic JavaScript: the future of Web Apps from Spike Brehm, Airbnb Engineering Nov. 2013

Single page apps have a single point of failure.

Lack SEO.

Suffer Performance Concerns.

7 Principles of Rich Web Applications from Guillermo Rauch, Nov. 2014

Server side rendering your React application

  • benefits your users in a tangible way
  • leverages your existing code

🚧 Demo 🚧

 Gif of Create React App loading on a slow connection

Likely, your client.js

import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";

ReactDOM.render(<App />, document.getElementById('root'));

and an index.html:

<!DOCTYPE html>
  <html>
  <head>
      <meta charset="utf-8">
      <title>SSR</title>
  </head>
  <body>
      <div id="root"></div>
      <script src="./client.js"></script>
  </body>
  </html>

🚧 Demo 🚧

A gif goes here of the Server rendered app
import React from "react";
import { renderToString } from "react-dom/server";
import App from "./components/App";

export default function template(props) {
  return `
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>SSR</title>
    </head>
    <body>
        <div id="root">${renderToString(<App {...props} />)}</div>
        <script src="./client.js"></script>
    </body>
    </html>
  `;
}

Add our template function to our response callback

import express from "express";
import template from "./template";

const app = express();

app.get("/*", (req, res) => {
  res.status(200).send(template())
});

app.listen(3000);
// client.js
import React from 'react'
import ReactDOM from "react-dom";
import App from './components/App';

ReactDOM.hydrate(<App />, document.getElementById('root'));

So that was simple 🌷 but that about 💐

  • Routing
  • Fetching Data

Considerations

  • Components only has a truncated lifecycle: constructor(), UNSAFE_componentWillMount() 😳
  • Essentially: no setState() 🚫
  • matchMedia, localStorage, fetch are all gone, kinda ☠️

Routing

In the case of React Router

On the client

ReactDOM.hydrate(
  <BrowserRouter>
    <App />
  </BrowserRouter>
)

& on the server

renderToString(
  <StaticRouter>
    <App />
  </StaticRouter>
)
function template({ location, ...props }) {
  const context = {}
  const app = renderToString(
  <StaticRouter location={location} context={context}>
    <App {...props} />
  </StaticRouter>
)
  return `
      <!DOCTYPE html>
      <html>
      <head>
          <meta charset="utf-8">
          <title>SSR</title>
      </head>
      <body>
          <div id="root">${app}</div>
          <script src="./client.js"></script>
      </body>
      </html>
  `;
}

app.get("/*", (req, res) => {
  const markup = template({ location: req.url })
  res.status(200).send(markup)
});

Fetching Data

  • Hoist your Data Fetching fetching outside your application
  • Leverage async/await
  • Coupling a component's data fetching to it's implementation is possible but 😖

Updating our response callback

async (req, res) => {
  const data = await fetch('endpoint/').then(res => res.json())
  const markup = renderToString(<App {...data} />)
  
  res.send(markup)
}

Coupling data-fetching implementation

class App extends React.Component {
  static async getInitialProps() {
    const data = await fetch('endpoint/').then(res => res.json())
    return { data }
  }
}

Authentication

🍪

You can grab your token from the server and call the same endpoint as that user.

async (req, res) => {
  const { TOKEN } = req.cookies
  const data = await fetch('endpoint/', { headers: { Authorization: TOKEN } }).then(res => res.json())
}

PRO-TIP

Don't travel alone. 🚀

Use a framework 🛠

Getting started with Next.js

  1. yarn add react react-dom next
  2. Add some scripts { "dev": "next" }
  3. Create pages/index.js
  4. 🔥🌎⚡️🌈🍷

Hooks and Suspense

const ThemeContext = React.createContext({ dark: true });

function App(props) {
  const [open, setOpen] = React.useState(false);
  const context = React.useContext(ThemeContext);

  return (
    <div
      className="App"
      style={{
        background: context.dark ? 'black' : 'white',
        color: context.dark ? 'white' : 'black'
      }}>
      <h1>Hello {props.name}</h1>
      {open && 'I AM OPEN'}
      <button onClick={() => setOpen(!open)}>Toggle</button>
    </div>
  );
}

Questions

Charles is a UI Engineer currently working on server-side rendered applications at Microsoft.

@charlespeters
charlespeters.net