DEV Community

Shiono Yoshihide
Shiono Yoshihide

Posted on

The React View Template Engine for Express

TLDR

  • Pass the server data to the React client props
  • Because this is a view template engine, so results can be searchable by searching engines like Google (yes, we use server side rendering)

GitHub logo saltyshiomix / react-ssr

React SSR as a view template engine

Overview

  • SSR (Server Side Rendering) as a view template engine
  • Dynamic props
    • Passing the server data to the React client props
    • Suitable for
      • Admin Panels
      • Blogging
  • Developer Experience
    • Zero config of webpack and babel
    • HMR (Hot Module Replacement) both scripts and even if styles when process.env.NODE_ENV !== 'production'
    • Built-in Sass (SCSS) support

Pros and Cons

Pros

Because it is just a view template engine:

  • It doesn't need to have any APIs, all we have to do is to pass the server data to the client
  • It supports multiple engines like .hbs, .ejs and React .(ts|js)x
  • We can use passport authentication as it always is

Cons

  • It is not so performant, because it assembles the whole HTML on each request
  • It does not support client side routing

Usage

With @react-ssr/express

Install it:

$ npm install --save @react-ssr/core @react-ssr/express express react react-dom
Enter fullscreen mode Exit fullscreen mode

And add a script to your package.json like this:

Enter fullscreen mode Exit fullscreen mode

Quick Start

Installation

$ npm install --save @react-ssr/express express react react-dom
Enter fullscreen mode Exit fullscreen mode

Populate package.json

{
  "scripts": {
    "start": "node server.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

Write server.js

const express = require('@react-ssr/express');
const app = express();

app.get('/', (req, res) => {
  const user = { name: 'World' };
  res.render('index', { user });
});

app.listen(3000, () => {
  console.log('> Ready on http://localhost:3000');
});
Enter fullscreen mode Exit fullscreen mode

Implement views/index.jsx

import React from 'react';

export default function Index(props) {
  return `Hello ${props.user.name}!`;
}
Enter fullscreen mode Exit fullscreen mode

Run Server

$ npm start
Enter fullscreen mode Exit fullscreen mode

You'll see Hello World!

Deep into Dive

1. Register jsx and tsx

source: register.ts

const ENGINE: 'jsx' | 'tsx' = getEngine();
app.engine(ENGINE, renderFile);
app.set('views', resolve(process.cwd(), viewsDir));
app.set('view engine', ENGINE);
app.use(express.static(distDir));
Enter fullscreen mode Exit fullscreen mode

2. Render a File (Server Side Rendering)

source: render.tsx

import { renderToString } from 'react-dom/server';

let html: string = '<!DOCTYPE html>';

let Page = require(file); // `file` is a React function component
Page = Page.default || Page;

html += renderToString(
  <Html script={`${hash}.js`}>
    <Page {...props} />
  </Html>
);

return html;
Enter fullscreen mode Exit fullscreen mode

3. Bundle Scripts and Write Output

source: render.tsx

import fs from 'fs';
import template from 'art-template';
import webpack from 'webpack';

const { ufs } = require('unionfs');
const MemoryFileSystem = require('memory-fs');
const template = require('art-template');

const cwd: string = process.cwd();

template.defaults.minimize = false;

const mfs = new MemoryFileSystem;
ufs.use(mfs).use(fs); // union memory-fs and fs!

// write file in the server memory
mfs.mkdirpSync(resolve(cwd, 'react-ssr-src'));
mfs.writeFileSync(resolve(cwd, `react-ssr-src/entry.jsx`), template(resolve(__dirname, '../page.jsx'), { props }));
mfs.writeFileSync(resolve(cwd, `react-ssr-src/page.jsx`), template(file, props));

// compile in the server memory!
const compiler: webpack.Compiler = webpack(configure(hash, ext, distDir));
compiler.inputFileSystem = ufs;
compiler.outputFileSystem = mfs;
compiler.run((err: any) => {
  if (err) {
    console.error(err.stack || err);
    if (err.details) {
      console.error(err.details);
    }
    return;
  }
});

// read the results from memory file system
// write the results to the real file system
await outputFileSync('result.js', mfs.readFileSync(cache).toString());
Enter fullscreen mode Exit fullscreen mode

That's it!

Final output html is like this:

<!DOCTYPE html>
<html>
  <body>
    <div id="app">
      <p>Hello World!</p>
    </div>
    <script src="/c834ab9b47260a08d695f59ba1a5b24d.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Ending

But I love to use NEXT.js! lol

Discussion (6)

Collapse
cyb3rsalih profile image
mehmet salih bindak

I am getting error at "Run Server" part.

" Cannot find module '@react-ssr/core/dist/document' "

Can't see any result in google either :(

Collapse
saltyshiomix profile image
Shiono Yoshihide Author

Have you installed @react-ssr/core?

If you want to see full information, please check out GitHub README, not this post :)

Collapse
cyb3rsalih profile image
mehmet salih bindak

Aaaah :(((( Sorry. I only installed @react-ssr/express. I thought it was a dependency and will install by itself. Now it is OK. Thank you for fast reply

Thread Thread
saltyshiomix profile image
Shiono Yoshihide Author

No problem, the latest information is at GitHub readme :)

Formally it's true that @react-ssr/express includes @react-ssr/core, but now it is separated for many reasons.

Collapse
icecoffee profile image
atulit023

Deep into Dive

  1. Register jsx and tsx source: register.ts

register.ts in point 1 is broken

Collapse
tomgrigory profile image
Tom George • Edited

const app = express();
^

TypeError: express is not a function

Why is it like this?????