Introduction
When we develop React apps, it is one of the best choices to separate a server and a client.
But in those cases, we must implement so many APIs for the client in the server side.
On the other hand, a good old way to server render views like handlebars or ejs is not suitable for React single page apps or server side rendering apps.
Though it is not possible to use NEXT.js as a view template engine like from an Express server, but it is necessary a little tricky techniques like this:
// pages/index.tsx
const IndexPage = ({ articles }) => {
return (
<ul>
{articles.map((article, index) => (
<li key={index}>{article.title}</li>
))}
</ul>
);
};
// we must use `getInitialProps()` to enable SSR correctly
IndexPage.getInitialProps = async ({ req, query }) => {
const isServer = !!req;
// and we have to check a process is a server or not
let articles;
if (isServer) {
// if the server side, we can receive data from the server directly
articles = query.articles;
} else {
// if the client side, we must fetch data from the API server
articles = await http.get('api/articles');
}
// finally, we can use data in the `IndexPage` component like above
return {
articles,
};
};
Have you ever thought of the implementation like this?
// server.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
const message = 'Hello World!';
res.render('index', { message });
});
app.listen(3000, () => {
console.log('> Ready on http://localhost:3000');
});
// views/index.jsx
export default function IndexPage{ message }) {
return <p>{message}</p>;
}
And if we could see 'Hello World' by SSR?
Let's Imagine!
// server.js
const posts = [
{ id: 1, body: 'This is a first post.' },
{ id: 2, body: 'This is a second post.' },
{ id: 3, body: 'This is a last post.' },
];
app.get('/', (req, res) => {
res.render('index', { posts });
});
app.get('/posts/:postId', (req, res) => {
const { postId } = req.params;
const post = findById(postId);
res.render('post', { post });
});
// views/index.jsx
import React from 'react';
const IndexPage = ({ posts }) => {
return (
<React.Fragment>
{posts.map((post, index) => {
return (
<p key={index}>
<a href={'/posts/' + post.id}>{post.body}</a>
</p>
);
})}
</React.Fragment>
);
};
export default IndexPage;
// views/post.jsx
import React from 'react';
const PostPage = ({ post }) => {
return (
<React.Fragment>
<p>{post.body}</p>
</React.Fragment>
);
};
export default PostPage;
Is it so easy enough, right?
And we can use React as if it was a view template engine!
About react-ssr
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
- Passing the server data to the React client
- 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
And add a script to your package.json like this:
Overview
- Pass the server data to the React client
props
- So it reacts as if it is a view template engine
- Off course, it is optimized for search engines by using server side rendering
- Developer Experience
- It is so easy to use and there is almost nothing to learn the way to use
- HMR (Hot Module Replacement) when
process.env !== 'production'
How to Use react-ssr
There are three npm packages for Express applications:
-
@react-ssr/core
- A core package for
@react-ssr/express
and@react-ssr/nestjs-express
- A core package for
-
@react-ssr/express
- A package for pure Express applications
-
@react-ssr/nestjs-express
- A package for NestJS Express applications
Usage of @react-ssr/express
in JavaScript
Installation:
$ npm install --save @react-ssr/core @react-ssr/express express react react-dom
In the package.json
:
{
"scripts": {
"start": "node server.js"
}
}
And populate files below inside your project:
.babelrc
:
{
"presets": [
"@react-ssr/express/babel"
]
}
server.js
:
const express = require('express');
const register = require('@react-ssr/express/register');
const app = express();
(async () => {
// register `.jsx` as a view template engine
await register(app);
app.get('/', (req, res) => {
const message = 'Hello World!';
res.render('index', { message });
});
app.listen(3000, () => {
console.log('> Ready on http://localhost:3000');
});
})();
views/index.jsx:
export default function IndexPage({ message }) {
return <p>{message}</p>;
}
That's it!
Then just run npm start
and go to http://localhost:3000
, you'll see Hello World!
.
Usage of @react-ssr/express
in TypeScript
To enable TypeScript engine (.tsx
), just put tsconfig.json
in your project root directory.
The codes of TypeScript will be like this:
package.json
:
{
"scripts": {
"start": "ts-node server.ts"
}
}
server.ts
:
import express, { Request, Response } from 'express';
import register from '@react-ssr/express/register';
const app = express();
(async () => {
// register `.tsx` as a view template engine
await register(app);
app.get('/', (req: Request, res: Response) => {
const message = 'Hello World!';
res.render('index', { message });
});
app.listen(3000, () => {
console.log('> Ready on http://localhost:3000');
});
})();
views/index.tsx
:
interface IndexPageProps {
message: string;
}
export default function IndexPage({ message }: IndexPageProps) {
return <p>{message}</p>;
}
Usage of @react-ssr/nestjs-express
Installation:
# install NestJS dependencies
$ npm install --save @nestjs/core @nestjs/common @nestjs/platform-express
# install @react-ssr/nestjs-express
$ npm install --save @react-ssr/core @react-ssr/nestjs-express react react-dom
In the package.json
:
{
"scripts": {
"start": "ts-node --project tsconfig.server.json server/main.ts"
}
}
And then, populate files below inside your project:
.babelrc
:
{
"presets": [
"@react-ssr/nestjs-express/babel"
]
}
tsconfig.json
:
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"jsx": "preserve",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"strict": true,
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true
},
"exclude": [
"node_modules",
"ssr.config.js",
".ssr"
]
}
tsconfig.server.json
:
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs"
},
"include": [
"server"
]
}
server/main.ts
:
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import register from '@react-ssr/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
:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
@Module({
controllers: [
AppController,
],
})
export class AppModule {}
server/app.controller.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 };
}
}
Finally, views/index.tsx:
interface IndexPageProps {
user: any;
}
const IndexPage = ({ user }: IndexPageProps) => {
return <p>Hello {user.name}!</p>;
};
export default IndexPage;
Then, just run npm start
and go to http://localhost:3000
, you'll see Hello NestJS!
.
There Are Many Examples
-
@react-ssr/express
.jsx
- examples/basic-jsx
- examples/basic-custom-views
- examples/basic-custom-document
- examples/basic-dynamic-head
- examples/basic-hmr-css
- examples/basic-hmr-scss
- examples/basic-blogging
- examples/with-jsx-antd
- examples/with-jsx-bulma
- examples/with-jsx-emotion
- examples/with-jsx-material-ui
- examples/with-jsx-semantic-ui
- examples/with-jsx-styled-components
.tsx
- examples/basic-tsx
- @react-ssr/nestjs-express
- @react-ssr/static
examples/with-jsx-antd
examples/with-jsx-emotion
examples/with-jsx-material-ui
examples/with-jsx-semantic-ui
examples/with-jsx-styled-components
Conclusion
Please try react-ssr and send us feedbacks!
Best,
Top comments (25)
I admire the post thanks for sharing!
On the subject, I have a framework agnostic alternative this should work with just about anything. send in a headless browser like chrome headless (puppeteer) to render the requested page, toString the output and send that as the response. In other words a much faster browser with no UI does the heavy lifting ahead of time and does cache for the next visitor which is even faster.
Oh, thank you for your sharing that!
The idea of intermediate puppeteer server is awesome :)
If you are OK, please let me know your framework!
Just download puppeteer to get started with headless chrome controlled by JavaScript. Then no matter what framework, given I use express, on route, point the browser here, get the rendered page as a string, store as a variable then serve that string. Not much else to it.
Like I say, no framework required just puppeteer and any http server.
Thank you letting me know!
It may be similar to react-native-dom, right? (If wrong, I'm sorry.)
I will try puppeteer later :)
Potentially? I am not a react specialist. Puppeteer is fantasticly useful, I have used it within a unit testing framework to create acceptance tests.
Does it rehydrate the JS on the client side?
Thank you for your comment!
It hydrates
id="react-ssr-root"
once :)I am interested in finding out what you mean by this? I am fairly new to development seeking out as much information as possible and this commend interested me since it seems like it pertains to performance.
So when you render javascript on the server, it just sends the markup and content to the browser. Rehydration means that the client side javascript "comes to life" and can function, rather than being static as was sent. It's not really related to performance, as far as I'm aware.
Thank you for your explanation!
In this point,
react-ssr
rehydrates DOM so we can use full React features like hooks :)I'm interested in the meaning of
rehydrate
, too.Anyway,
react-ssr
hydrates a DOM target once :)What you made is pretty cool. I just spent the last few hours playing with it, but I think you need to point out in your documentation that this does not support client-side routing. This may be obvious to some people, but it wasn't for me. I won't say it was a waste of time because overall I like what you built, but I feel like when we come across a tool named
react-ssr
we're going to assume that it's not purely server-side rendering unless otherwise noted.Thank you for your comment!
I totally agree with what you pointed out. I'll improve README until next minor release :)
If you have any troubles, feel free to open issues like this comment!
Hey, thanks for sharing!
You said it can replace NextJS, so what about client side routing ? Do you need to add smth like react-router/reach-router and then bind on the same routes you already declared in Express?
Thank you for pointing it out!
It may be too much to say
alternative
, because I don't think it can replace all roles of NEXT.js.But it may be or can be a choice to use
react-ssr
for small projects.This project aims to make the new way to use React, as a view template engine. It renders views and passes server data to the client like ejs or handlebars, so we should not use this library if we have to use client side routing so many.
I think the practical usage is one of the blows:
Another option for backend is tsed.io/
Cool stuff, Shiono!
For some reason it looks familiar as it seems close to ASP.NET, where React is being used in place of Razor. Would such an association be an accident? π
I'm a .NET developer too, so I know Razor but I made a similar project accidentally :)
For React, server side rendering is mainly for Node.js. I think Razor made a big thing to use SSR React applications with not Node.js. I admire.
You've created something fun "accidentally" in a good way :)
This is so cool! thank you for sharing and great project π¦
Thank you very much, Saurabh!
I just published
v0.19.0
and added @react-ssr/static which can generate static files.If you like, feel free to send me feedbacks :)
ππΎππΎππΎ. You're a life saver. Been looking for something like this, almost gave to start something similar on my own.
What a great article covering an amazing new technology. I am going to give this a try today!
Thank you very much, Macro!
Please check out this "good old way" server rendered view technology :)
Bro I have seen your nextron project and it is dope, can you tell me how you make a cli tool, create-nextron-app .