npm package: @react-ssr-with-less/nestjs-express
Description: React SSR as a view template engine
Author: Yoshihide Shiono
Latest version: 0.22.1

## 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](http://www.passportjs.org) 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

Install it:

```bash
# install NestJS dependencies
$ npm install --save @nestjs/core @nestjs/common @nestjs/platform-express reflect-metadata rxjs

# install @react-ssr-with-less/nestjs-express
$ npm install --save @react-ssr-with-less/core @react-ssr-with-less/nestjs-express react react-dom
```

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

```json
{
  "scripts": {
    "start": "ts-node --project tsconfig.server.json server/main.ts"
  }
}
```

Then, populate files below inside your project:

**`tsconfig.json`**:

```json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "jsx": "preserve",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "strict": true,
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  },
  "exclude": [
    "node_modules",
    "ssr.config.js",
    "dist",
    ".ssr"
  ]
}
```

**`tsconfig.server.json`**:

```json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "commonjs",
    "outDir": "dist"
  },
  "include": [
    "server"
  ]
}
```

**`server/main.ts`**:

```ts
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import register from '@react-ssr-with-less/nestjs-express/register';
import { AppModule } from './app.module';

(async () => {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);

  // register `.tsx` as a view template engine
  await register(app);

  app.listen(3000, async () => {
    console.log(`> Ready on http://localhost:3000`);
  });
})();
```

**`server/app.module.ts`**:

```ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';

@Module({
  controllers: [
    AppController,
  ],
})
export class AppModule {}
```

**`server/app.controller.ts`**:

```ts
import {
  Controller,
  Get,
  Render,
} from '@nestjs/common';

@Controller()
export class AppController {
  @Get()
  @Render('index') // this will render `views/index.tsx`
  public showHome() {
    const user = { name: 'NestJS' };
    return { user };
  }
}
```

**`views/index.tsx`**:

```tsx
interface IndexProps {
  user: any;
}

const Index = ({ user }: IndexProps) => {
  return <p>Hello {user.name}!</p>;
};

export default Index;
```

Finally, just run `npm start` and go to `http://localhost:3000`, and you'll see `Hello NestJS!`.

## Configuration (`ssr.config.js`)

Here is the default `ssr.config.js`, which is used by `react-ssr` when there are no valid values:

```js
module.exports = {
  id: 'default',
  distDir: '.ssr',
  viewsDir: 'views',
  staticViews: [],
  webpack: (config /* webpack.Configuration */, env /* 'development' | 'production' */) => {
    return config;
  },
};
```

### `ssr.config.js#id`

The id of **UI framework**. (default: `default`)

It can be ignored only when the project does not use any UI frameworks.

Supported UI frameworks are:

- [x] default (the id `default` doesn't need to be specified in `ssr.config.js`)
  - [x] [bulma](https://bulma.io)
  - [x] [semantic-ui](https://react.semantic-ui.com)
  - [x] Or any other **non** CSS-in-JS UI frameworks
- [x] [emotion](https://emotion.sh)
- [x] [styled-components](https://www.styled-components.com)
- [x] [material-ui](https://material-ui.com)
- [x] [antd](https://ant.design)
- [ ] and more...

For example, if we want to use `emotion`, `ssr.config.js` is like this:

```js
module.exports = {
  id: 'emotion',
};
```

### `ssr.config.js#distDir`

The place where `react-ssr` generates **production** results. (default: `.ssr`)

If we use TypeScript or any other library which must be compiled, the config below may be useful:

```js
module.exports = {
  // dist folder should be ignored by `.gitignore`
  distDir: 'dist/.ssr',
};
```

### `ssr.config.js#viewsDir`

The place where we put views. (default: `views`)

A function `res.render('xxx')` will render `views/xxx.jsx` or `views/xxx.tsx`.

A working example is here: [examples/basic-custom-views](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-custom-views)

### `ssr.config.js#staticViews`

If specified, `react-ssr` generates html cache when production:

```js
module.exports = {
  staticViews: [
    'auth/login',
    'auth/register',
    'about',
  ],
};
```

### `ssr.config.js#webpack()`

```js
module.exports = {
  webpack: (config /* webpack.Configuration */, env /* 'development' | 'production' */) => {
    // we can override default webpack config here
    return config;
  },
};
```

## Custom `process.env.NODE_ENV`

If you set `process.env.REACT_SSR_ENV`, you can separate `process.env.NODE_ENV` from react-ssr:

**package.json**

```json
{
  "scripts": {
    "start": "cross-env NODE_ENV=k8s REACT_SSR_ENV=production node dist/main.js"
  }
}
```

## Custom Babel Config

We can extends its default `.babelrc` like this:

**`.babelrc`**:

```json
{
  "presets": [
    "@react-ssr-with-less/nestjs-express/babel"
  ]
}
```

A working example is here: [examples/basic-custom-babelrc](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-custom-babelrc)

## Custom App

Just put `_app.tsx` into the views root:

**`views/_app.tsx`**:

```jsx
// we can import global styles or use theming
import '../styles/global.scss';

const App = (props) => {
  // yes, this `props` contains data passed from the server
  // and also we can inject additional data into pages
  const { children, ...rest } = props;

  // we can wrap this PageComponent for persisting layout between page changes
  const PageComponent = children;

  return <PageComponent {...rest} />;
};

export default App;
```

A working example is here:

- [examples/basic-custom-app](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-custom-app)
- [examples/with-jsx-emotion](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-emotion)
- [examples/with-jsx-material-ui](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-material-ui)

## Custom Document

Just put `_document.tsx` into the views root:

**`views/_document.tsx`**:

```tsx
import React from 'react';
import {
  Document,
  Head,
  Main,
} from '@react-ssr-with-less/nestjs-express';

export default class extends Document {
  render() {
    return (
      <html lang="en">
        <Head>
          <title>Default Title</title>
          <meta charSet="utf-8" />
          <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
          <link rel="shortcut icon" href="/favicon.ico" />
        </Head>
        <body>
          <Main />
        </body>
      </html>
    );
  }
};
```

**Note**:

- **Please put `<Main />` component directly under `<body>` tag and don't wrap `<Main />` component with another components**, because this is a hydration target for the client.

And then, use it as always:

**`views/index.tsx`**:

```tsx
const Index = (props) => {
  return <p>Hello World!</p>;
};

export default Index;
```

A working example is here: [examples/basic-custom-document](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-custom-document)

## Dynamic `Head`

We can use the `Head` component in any pages:

**`views/index.tsx`**:

```tsx
import React from 'react';
import { Head } from '@react-ssr-with-less/nestjs-express';

const Index = (props) => {
  return (
    <React.Fragment>
      <Head>
        <title>Dynamic Title</title>
        <meta name="description" content="Dynamic Description" />
      </Head>
      <p>Of course, SSR Ready!</p>
    </React.Fragment>
  );
};

export default Index;
```

A working example is here: [examples/basic-dynamic-head](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-dynamic-head)

## Supported UI Framework

- [x] default (the id `default` doesn't need to be specified in `ssr.config.js`)
  - [x] [bulma](https://bulma.io)
  - [x] [semantic-ui](https://react.semantic-ui.com)
  - [x] Or any other **non** CSS-in-JS UI frameworks
- [x] [emotion](https://emotion.sh)
- [x] [styled-components](https://www.styled-components.com)
- [x] [material-ui](https://material-ui.com)
- [x] [antd](https://ant.design)
- [ ] and more...

### Non CSS-in-JS framework

<p align="center"><img src="https://i.imgur.com/0PwlfVk.png"></p>

Like [semantic-ui](https://react.semantic-ui.com), non CSS-in-JS frameworks are supported without extra configuration.

All we have to do is to load global CSS in `_document` or each page:

**`views/_document.tsx`**:

```tsx
import React from 'react';
import {
  Document,
  Head,
  Main,
} from '@react-ssr-with-less/express';

export default class extends Document {
  render() {
    return (
      <html>
        <Head>
          <title>A Sample of Semantic UI React</title>
          <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css" />
        </Head>
        <body>
          <Main />
        </body>
      </html>
    );
  }
}
```

### With Ant Design

<p align="center"><img src="https://i.imgur.com/yWGxseM.png"></p>

In order to enable SSR, we must install `babel-plugin-import` as devDependencies.

And then, populate `.babelrc` in your project root:

```json
{
  "presets": [
    "@react-ssr-with-less/express/babel"
  ],
  "plugins": [
    [
      "import",
      {
        "libraryName": "antd",
        "style": "css"
      }
    ]
  ]
}
```

A working example is here: [examples/with-jsx-antd](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-antd)

### With Emotion

<p align="center"><img src="https://i.imgur.com/XN8evEM.png"></p>

In order to enable SSR, we must install these packages:

- [@emotion/cache](https://npm.im/@emotion/cache) as **dependencies**
- [create-emotion-server](https://npm.im/create-emotion-server) as **dependencies**
- [babel-plugin-emotion](https://npm.im/babel-plugin-emotion) as devDependencies

And then, populate `.babelrc` in your project root:

```json
{
  "presets": [
    "@react-ssr-with-less/express/babel"
  ],
  "plugins": [
    "emotion"
  ]
}
```

A working example is here: [examples/with-jsx-emotion](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-emotion)

### With Material UI

<p align="center"><img src="https://i.imgur.com/o1AWdyd.png"></p>

We can use [material-ui](https://material-ui.com) without extra configuration.

A working example is here: [examples/with-jsx-material-ui](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-material-ui)

### With styled-components

<p align="center"><img src="https://i.imgur.com/RtuijYA.png"></p>

In order to enable SSR, we must install `babel-plugin-styled-components` as devDependencies.

And then, populate `.babelrc` in your project root:

```json
{
  "presets": [
    "@react-ssr-with-less/express/babel"
  ],
  "plugins": [
    "styled-components"
  ]
}
```

A working example is here: [examples/with-jsx-styled-components](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-styled-components)

## Examples

### @react-ssr-with-less/express

**.jsx**

- [examples/basic-jsx](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-jsx)
- [examples/basic-custom-app](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-custom-app)
- [examples/basic-custom-babelrc](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-custom-babelrc)
- [examples/basic-custom-document](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-custom-document)
- [examples/basic-custom-views](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-custom-views)
- [examples/basic-dynamic-head](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-dynamic-head)
- [examples/basic-hmr-css](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-hmr-css)
- [examples/basic-hmr-scss](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-hmr-scss)
- [examples/basic-blogging](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-blogging)
- [examples/basic-multiple-view-engines](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-multiple-view-engines)
- [examples/with-jsx-antd](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-antd)
- [examples/with-jsx-bulma](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-bulma)
- [examples/with-jsx-emotion](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-emotion)
- [examples/with-jsx-material-ui](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-material-ui)
- [examples/with-jsx-semantic-ui](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-semantic-ui)
- [examples/with-jsx-styled-components](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-styled-components)

**.tsx**

- [examples/basic-tsx](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-tsx)
- [examples/with-tsx-redux-todo](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-tsx-redux-todo)

### @react-ssr-with-less/nestjs-express

- [examples/basic-nestjs](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-nestjs)
- [examples/basic-nestjs-nodemon](https://github.com/saltyshiomix/react-ssr/tree/master/examples/basic-nestjs-nodemon)

## Real World Examples

- [react-ssr-jsx-starter](https://github.com/saltyshiomix/react-ssr-jsx-starter)
- [react-ssr-tsx-starter](https://github.com/saltyshiomix/react-ssr-tsx-starter)
- [react-ssr-nestjs-starter](https://github.com/saltyshiomix/react-ssr-nestjs-starter)
- [react-ssr-redux-todo-app](https://github.com/saltyshiomix/react-ssr-redux-todo-app)

## Articles

[Introducing an Alternative to NEXT.js](https://dev.to/saltyshiomix/introducing-an-alternative-to-next-js-12ph)

[[Express] React as a View Template Engine?](https://dev.to/saltyshiomix/express-react-as-a-view-template-engine-h37)

## Related

[reactjs/express-react-views](https://github.com/reactjs/express-react-views)

## Contact

- via [GitHub Issues](https://github.com/saltyshiomix/react-ssr/issues)
- via [Twitter](https://twitter.com/saltyshiomix)