The easiest way to move your React application to Server-Side Rendering. Handles Side Effects and synchronizes State.

Overview

iSSR

The easiest way to move your React application to Server-Side Rendering. Handles Side Effects and synchronizes State.

Table of Contents

Articles

Features

  • iSSR supports native setState, Redux (thunk, sagas), Mobx, Apollo and other state management libraries
  • TypeScript support
  • Small size (5kb)
  • No dependencies

Getting Started

Modern JS applications are divided into 2 types:

  • CSR - Client-Side rendering. The application will be displayed only after downloading and executing all the necessary JS code. Until then, the user will see a blank page. It degrades the UX and is bad for SEO.
  • SSR - Server-Side rendering. The auxiliary server doesn't send a blank page, but a page with data. Thus, the user can immediately start working with the application, and search engine bots will index the page.

SSR

Schematically, the SSR application looks like this:

iSSR

  • SSR application consists of two sub-applications - frontend and backend with common logic.
  • NodeJS app runs React app.
  • iSSR handles all asynchronous operations.
  • After receiving data from asynchronous operations, the React application is rendered.
  • NodeJS application serves HTML to the user.

Problems

One of the key problems with SSR applications are asynchronous operations. JS is an asynchronous language, all requests to the server, on which our application data depends, are asynchronous. They affect the state of the system - these are side effects. Since content availability is critical for search engines, we must handle this asynchronous behavior. The React Server Renderer is designed as a synchronous operation that steps through our React-DOM step by step and turns it into HTML.

The second problem is hydration. A process that allows us to associate the received HTML and the state of the application from the server with what will be built in the user's browser.

iSSR handles asynchronous operations and synchronizes state on the client.

Motivation

React currently has many solutions for building SSR applications. The most popular solution is Next.JS. This is a great framework with many possibilities, iSSR cannot replace it. But, Next.JS requires rewriting your existing application completely. Next.JS is a framework, which means you have to use its approaches. iSSR is just a small library that handles side effects and synchronizes state.

  • You can very quickly migrate your existing application to SSR using iSSR without major changes.
  • You can use any build system.
  • You can use any state management solution like Redux, Apollo, Mobx or native setState.
  • You can use any other SSR libraries (for example @loadable, react-helmet, etc).

Using

The simplest example of an SSR application using an asynchronous function via setState

Example:

Here is a simple Todo List Application without SSR. It uses jsonplaceholder for mocking the data:

import React, { useState, useEffect } from 'react';
import { render } from 'react-dom';

const getTodos = () => {
  return fetch('https://jsonplaceholder.typicode.com/todos')
   .then(data => data.json())
};

const TodoList = () => {
  const [todos, setTodos] = useState([]);

  useEffect(() => {
    getTodos()
      .then(todos => setTodos(todos))
  }, []);

  return (
    <div>
      <h1>Hi</h1>
      <ul>
        {todos.map(todo => (
          <li key={todo.id} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>{todo.title}</li>
        ))}
      </ul>
    </div>
  )
}

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

It's very simple, when we open the application it will load the todo list data from the server and render it.

Let's change this app to SSR:

Step 1. Installation:

npm install @issr/core --save
npm install @issr/babel-plugin --save-dev

Basic webpack configuration for SSR:

npm install @babel/core @babel/preset-react babel-loader webpack webpack-cli nodemon-webpack-plugin --save-dev

For this example we should install node-fetch because native fetch does not support node.js. Also, for the server we will use express, but you can use any module

npm install node-fetch express --save

Step 2. Make webpack.config.js in the root of project

const path = require('path');
const NodemonPlugin = require('nodemon-webpack-plugin');

const commonConfig = {
  module: {
    rules: [
      {
        test: /\.jsx$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: [
                '@babel/preset-react'
              ],
              plugins: [
                '@issr/babel-plugin'
              ]
            }
          }
        ]
      }
    ]
  },
  resolve: {
    extensions: [
      '.js',
      '.jsx'
    ]
  }
}

module.exports = [
  {
    ...commonConfig,
    target: 'node',
    entry: './src/server.jsx',
    output: {
      path: path.resolve(__dirname, './dist'),
      filename: 'index.js',
    },
    plugins: [
      new NodemonPlugin({
        watch: path.resolve(__dirname, './dist'),
      })
    ]
  },
  {
    ...commonConfig,
    entry: './src/client.jsx',
    output: {
      path: path.resolve(__dirname, './public'),
      filename: 'index.js',
    }
  }
];

The main goal is to create 2 applications client and server with common logic.

Step 3. Let's separate the general logic from rendering. Let's create App.jsx, and take out the common part for both Frontend and Backend:

import React from 'react';
import fetch from 'node-fetch';
import { useSsrState, useSsrEffect } from '@issr/core';

const getTodos = () => {
  return fetch('https://jsonplaceholder.typicode.com/todos')
   .then(data => data.json())
};

export const App = () => {
  const [todos, setTodos] = useSsrState([]);

  useSsrEffect(async () => {
    const todos = await getTodos()
    setTodos(todos);
  });

  return (
    <div>
      <h1>Hi</h1>
      <ul>
        {todos.map(todo => (
          <li key={todo.id} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
};

In this code, getTodos is an asynchronous operation that makes call to the jsonplaceholder server and gets the todo list data.

  • useSsrState is analogue of useState only with SSR support

  • useSsrEffect is analogue useEffect (() => {}, []); for SSR. It works with any async logic.

Step 4. client.jsx should contain part of the application for Frontend

import React from 'react';
import { hydrate } from 'react-dom';
import createSsr from '@issr/core';
import { App } from './App';

const SSR = createSsr(window.SSR_DATA);

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

The code:

const SSR = createSsr(window.SSR_DATA);

Associates the state executed on the server with the application on the client side. For correct work useSsrState on the client

Step 5. server.jsx should contain the logic of the NodeJS application, it is convenient to use the koa/express framework or similar for this:

import React from 'react';
import express from 'express';
import { serverRender } from '@issr/core';
import serialize from 'serialize-javascript';
import { App } from './App';

const app = express();

app.use(express.static('public'));

app.get('/*', async (req, res) => {
  const { html, state } = await serverRender(() => <App />);

  res.send(`
  <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
      window.SSR_DATA = ${serialize(state, { isJSON: true })}
    </script>
</head>
<body>
    <div id="root">${html}</div>
    <script src="/index.js"></script>
</body>
</html>
`);
});

app.listen(4000, () => {
  console.log('Example app listening on port 4000!');
});

There are 2 important points in this code:

app.use(express.static('public'));

The server should serve the folder where frontend part of application is built.

<script>
  window.SSR_DATA = ${serialize(state, { isJSON: true })}
</script>

This code saves the executed state on the server to use it on the client side later .

Step 6 The final step is webpack's scripts for development mode and building. Add to your package.json:

"scripts": {
  "start": "webpack -w --mode development",
  "build": "webpack"
},

  • Please see Articles to learn how implement SSR for Redux/Sagas/Thunks and other.
  • Please see "examples" folder to learn other cases - here

The MIT License

Copyright (c) Aleksandrov Sergey

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

You might also like...
Jed Saylor minting dapp is a quick and easy way to connect your smart contract and start minting NFTs.
Jed Saylor minting dapp is a quick and easy way to connect your smart contract and start minting NFTs.

Welcome to Jed Saylor 👾 All the code in these repos was created and explained by Jed Saylor on his course. To find help please visit: 📺 Instagram Je

A guide to building your own React stack, explaining options and tradeoffs along the way
A guide to building your own React stack, explaining options and tradeoffs along the way

Custom React Stack React has a very rich ecosystem. For anything you want to do, there is probably a library or a framework available for it. That's g

✉️ Display e-mails in your React.js projects. (Targets Gmail rendering.)
✉️ Display e-mails in your React.js projects. (Targets Gmail rendering.)

react-letter is a React.js component that allows for an easy display of HTML e-mail content with automatic sanitization. Support for features should m

Simplest way to add twitter widgets to your react project.

React Twitter Embed Component React Twitter Embed Component Simplest way to add Twitter Widgets to your react project. Demo and Examples https://saura

Github-profile-readme-maker - Best Profile Generator, Create your perfect GitHub Profile ReadMe in the best possible way
Github-profile-readme-maker - Best Profile Generator, Create your perfect GitHub Profile ReadMe in the best possible way

GPRM : GitHub Profile ReadMe Maker Features We got everything that you need ! Cr

Uses TypeScript and Proxy to dynamically construct a lens-like interface for your application state.

Lenses in React Uses TypeScript and Proxy to dynamically construct a lens-like interface for your application state. Example You can construct a lens/

In this demo, I am using a library created by me, called 'aesthetic-state' for global state managment in React

This is a Next.js project bootstrapped with create-next-app. Getting Started First, run the development server: npm run dev # or yarn dev Open http://

OpenSea-telegrambot - React application with Express server

React application with Express server This project send message to telegram if t

A javascript framework to share url to social media sites like facebook, twitter, reddit, whastapp in an easy and simple way.

simple-sharer [by BUILDBROTHERS.COM] A javascript framework to share url to social media sites like facebook, twitter, reddit, whastapp in an easy and

Comments
  • TypeError: o is not a function

    TypeError: o is not a function

    Hello,

    I am trying to use useSsrState in react ssr that is setup in Azure function with Javascript. Since Azure funciton with Javascript doesn't support any module javascript like import issrPkg from '@issr/core'; I have to use .mjs for each file.

    After I create a page like these: import issrPkg from '@issr/core'; const { useSsrState, useSsrEffect } = issrPkg; const BackupRestoreChecklistDate = (content) => { const [backupRestoreChecklistDateDisplay, setbackupRestoreChecklistDateDisplay] = useSsrState("test"); return ( createElement("div", {key: generateReactKeys()}, "dasfgdfgdfg") ) } export default BackupRestoreChecklistDate;

    it gave me the error of TypeError: Cannot read property 'length' of null,TypeError: o is not a function

    The version is "@issr/core": "^1.1.0",

    Is there any fix for this setup? Thanks

    opened by hyphen1370 2
  • Infinite Loop using Typescript

    Infinite Loop using Typescript

    I starting using typescript, but getting infinity loop in useSsrEffect.

    webpack.config.js

    const path = require('path');
      const NodemonPlugin = require('nodemon-webpack-plugin');
    
    const commonConfig = {
     module: {
       rules: [
         {
           test: /\.(scss|css)$/,
           use: ['css-loader', 'sass-loader'],
         },
         {
           test: /\.(ts|tsx)$/,
           use: 'ts-loader',
           exclude: /node_modules/,
         },
         {
           test: /\.jsx$/,
           exclude: /node_modules/,
           use: [
             {
               loader: 'babel-loader',
               options: {
                 presets: ['@babel/preset-react'],
                 plugins: ['@issr/babel-plugin'],
               },
             },
           ],
         },
       ],
     },
     resolve: {
       extensions: ['.tsx', '.ts', '.js', '.jsx', '.scss'],
     },
    };
    
    module.exports = [
     {
       ...commonConfig,
       target: 'node',
       entry: './src/server.jsx',
       output: {
         path: path.resolve(__dirname, './dist'),
         filename: 'index.js',
       },
       plugins: [
         new NodemonPlugin({
           watch: path.resolve(__dirname, './dist'),
         }),
       ],
     },
     {
       ...commonConfig,
       entry: './src/client.tsx',
       output: {
         path: path.resolve(__dirname, './public'),
         filename: 'index.js',
       },
     },
    ];
    

    App.tsx

    import React, { useEffect } from 'react';
    import { useSsrState, useSsrEffect } from '@issr/core';
    import './styles/index.scss';
    
    const asyncFn = () =>
      new Promise((resolve) => setTimeout(() => resolve({ data: 'Cocker Spaniel' }), 1000));
    
    export const App = () => {
      const [dog, setDog] = useSsrState('Dog image goes here');
      
      useEffect(() => {
        asyncFn().then((data) => {
          console.log(data);
        });
      }, []);
    
      useSsrEffect(async () => {
        asyncFn().then((data) => {
          console.log(data);
        });
    
        console.log('LOOP');
        setDog('LOOP');
      });
    
      return (
        <div>
          <h1>Random dog</h1>
          <img src={dog} height="150" />            
        </div>
      );
    };
    
    
    Screenshot 2021-02-03 at 14 16 00

    Where I can find example with typescript?

    opened by Erihon78 2
  • fix boolean state

    fix boolean state

    boolean state not work correctly

    example: const [isLoading, setIsLoading] = useSsrState(true); useSsrEffect(async () => { setIsLoading(false); });

    isLoading - alway "true"

    opened by Dimazzz 1
  • Setter function from useSsrState not working as expected

    Setter function from useSsrState not working as expected

    From React API docs:

    [When setting a state using hooks] if the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value

    That means, if we had a count state, for example, and we wanted to increase its value by 1, we would have to necessarily know the current state, so in this case the solution is to pass a function to setCountwhich gets the current value and return the next one:

    setCount(currentCount => currentCount + 1)

    This works totally fine when using the useState hook from React. Unfortunately useSsrState from iSSR does not work as expected. After the first render if we try to update the state, it only changes once. In this case, being 0 the initial value of count and after using setCount(currentCount => currentCount + 1) once, count's value now becomes 1. This is expected, but then if we try to set count again, it doesn't work anymore, so the value stays in 1.

    opened by JulianSoto 1
Releases(2.0.0)
Owner
Sergey
Sergey
HTML meta tags for React-based apps. Works for both client- and server-side rendering, and has a strict but flexible API.

React Document Meta HTML meta tags for React-based apps. Works for both client- and server-side rendering, and has a strict but flexible API. Built wi

kodyl 321 Oct 26, 2022
React ESI: Blazing-fast Server-Side Rendering for React and Next.js

React ESI: Blazing-fast Server-Side Rendering for React and Next.js React ESI is a super powerful cache library for vanilla React and Next.js applicat

Kévin Dunglas 628 Nov 10, 2022
Adds server side rendering support to React Relay

Isomorphic React Relay Enables server-side rendering of React Relay containers. If you use react-router-relay you might also become interested in isom

Denis Nedelyaev 246 May 22, 2022
Broprint.js - The world's easiest, smallest and powerful visitor identifier for browsers

This package generates a unique ID/String for different browsers. Like chrome, Firefox or any other browsers which support `canvas` and `audio` fingerprinting. You can easily do the browser fingerprinting with this library. Its small and minimal.

Rajesh Royal 58 Nov 15, 2022
A simple way to write re-usable features with React + EffectorA simple way to write re-usable features with React + Effector

Effector Factorio The simplest way to write re-usable features with React + Effector Install npm install effector-factorio Why this? People became to

Anton Kosykh 33 Nov 19, 2022
null 1 Apr 28, 2022
VFX Composer (formerly known as three-vfx, or 3VFX) is a visual effects library for Three.js and react-three-fiber

VFX Composer (formerly known as three-vfx, or 3VFX) is a visual effects library for Three.js and react-three-fiber. It aims to be highly performant (with effects almost entirely simulated on the GPU) and easy to extend.

Hendrik Mans 245 Nov 13, 2022
An algebraic effects library for javascript and typescript using generators

Algebraify Algebraic Effects Are Here! (sort of) (If you're unfamiliar with algebraic effects, here's a great article: Algebraic Effects for the Rest

Jackie Edwards 72 Sep 19, 2022
vfx - a visual effects library for react-three-fiber

vfx is a visual effects library for react-three-fiber. It aims to be highly performant (with effects almost entirely simulated on the GPU) and easy to extend.

Hendrik Mans 245 Nov 13, 2022
This project features basic simulations that show the effects of social distancing

This project features basic simulations that show the effects of social distancing

Veselin Stoyanov 2 Feb 1, 2022