Zum Inhalt springen

Server Side React.js with node.js

What is React SSR?

React.js is ideal for creating interactive components in the browser. It’s tempting to use it to build websites, such as e-commerce sites, but Google can’t index these types of sites. Why? Because the React.js UI is generated by a JavaScript library running in the browser, while Google examines the HTML emitted by the server, before it is transformed in the browser.

This is where React SSR becomes interesting, as it allows you to use React.js on the server side.

The process is as follows:

  1. We create a server-side UI in React, which is transformed into pure HTML.
  2. Google sees an HTML code whose visual appearance is completely identical to what we will have in the browser once the real React.js enabled.
  3. The browser fully loads the required JavaScript resources.
  4. Once fully loaded, the real one replaces the fake React component.

The person browsing our website sees no difference between before and after, while Google is happy and indexes our website correctly. Also, for the visitor of your website, the page seems to load instantly. Loading is much faster than with a real React component, we can easily have a two-second difference, even with a fast internet connection.

What will we see?

We will see :

  • What is the matter with React SSR and Node.js
  • Jopi Loader: which allows importing CSS and images with Node.js.
  • Jopi Rewrite: a simple framework with React SSR support.
  • We will create a full sample project.

The matter with Node.js and React

The problem 😰

The bad with Node.js is that it doesn’t support importing CSS, nor other resources like images. It only allows importing JavaScript. This impossibility does that our capacities are limited, also we can’t import a code library designed for the browser.

With pure Node.js, doing this is not possible:

import React, {useEffect} from 'react';
import * as ReactServer from 'react-dom/server';

// Not supported with Node.js :-(
import style from "my-component.module.css";

// Not supported with Node.js :-(
// With Vite.js, it returns the url of the image 
// or a base64 encoded image. (depending on this image size)
import myImg from "./my-image.png?inline";

function MyComponent() {
    return <div className={style.myComponent}>
            A component using CSS module.</div>;
}

console.log(ReactServer.renderToStaticMarkup(<MyComponent />));

The solution 👍

Hopefully, the last versions of Node.js allow extending his capacities through a plugin system. Usage of a plugin allows enabling this special import, which is only allowed by bundlers like Vite.js / WebPack.

Here we will discover a plugin named jopi-loader which role is to allows code compatibility with Vite.js and WebPack, but for server site node.js.

Jopi Loader

What is it?

Jopi Loader is a plugin for Node.js, which allows supporting custom import, like importing CSS and images. It mimics Vite.js import capacities, doing that most of what compile with Vite.js (for browser) compile with Jopi Loader (for server).

With Jopi Loader, you can do all this on the server-side:

  • Import simple css/scss: import "./my-css.scss".
  • Import css/scss modules import style from "style.module.scss".

  • Importing images/text/… :

    • import serverFilePath from "./my-image.png"
      (returns an url pointing to this image)
    • import asDataUrl from "./my-image.png?inline"
      (returns image a base64 encoded string)
    • import asRawText from "./my-text.txt?raw"
      (returns image a data-url)

See Vites.js doc for more information and samples.

How does it work?

When doing React.js for the browser, you generally use a tool like Vite.js or WebPack. These tools analyze your source code and replace all import in the headers of your script, selecting those which are not pointing on JavaScript code. In result, it generates transformed files in an output directory.

Jopi Loader doesn’t work the same way, since it uses a capacity of Node.js allowing us to transform all the import that the engine found. Each time Node.js found an import in a new script, he calls Jopi Loader, which rewrites it if needed. It does it without generating temporary files, in a very optimized way.

It’s how Jopi Loader is able to add mechanisms allowing custom imports.

How to use it?

You only need to use jopin where you use node.

  • Where you do node --import jopi-loader ./myscript.js
  • Then you do npx jopin ./myscript.js

jopin sent the exact same arguments to node.js but add the options required to import jopi-loader plugin.
It also enables watching and auto-restart if you are in dev mode.

Jopi Rewrite

What is it?

Jopi Rewrite is a web server for Node.js, which is optimized for Bun.js. He is very (very) fast with Bun.js, and fast with Node.

Jopi Rewrite comes packed with a lot of useful features, like :

  • Advanced React SSR (what we will see here)
  • Native Tailwind support (we will also see it)
  • Authentification (using JWT)
  • User directory
  • Reverse proxy with load-balancing
  • Customizable page cache
  • DDOS attack protection (including slowloris)
  • … and a lot more interesting things!

What makes Jopi Rewrite very great is his simplicity. Since the v2, an „intent API“ has been added. It takes advantage of the IDE’s auto-completion capabilities to only offer choices that are relevant to what you want to achieve (your intent).

About Windows support

Jopi Rewrite is not optimized for Windows (also the Windows version isn’t rigorously tested) but works great with WSL. If you are a Windows user, prefer using WSL (Windows Subsystem for Linux) which executes all this much faster.

Sample project

Here we will create a small project, which first version will only expose a web page printing a simple message. Once this first version is ok, we will add real React SSR capabilities.

Step 1 — The index.tsx file

The first thing to do, is to create a file index.tsx with our source code.
Here we will use TypeScript.

import {jopiApp} from "jopi-rewrite";
import React from "react";

jopiApp.startApp(jopiEasy => {
  // Create the website.
  jopiEasy.new_webSite("http://127.0.0.1:3000")
          // Will respond on path "/" (our website root)
          .add_path("/")
            // Define what to do for a GET call.
            .onGET(async req => {
              // Here React is used as a simple template engine.
              // (it's the most basic form of React SSR)
              let reactAsHTML = req.reactToString(
                  <div>Hello from react!</div>);

              return req.htmlResponse(reactAsHTML);
            })
            .DONE_add_path()
          .DONE_createWebSite();
})

Step 2 — The package.json file

Once done, you need to create a package.json which represent our project root point when node.js execute.

Here is the content of this file:

{
  "dependencies": {
    "jopi-rewrite": "^2.0.23",
    "react": "^19.1.1",
    "react-dom": "^19.1.1"
  },
  "devDependencies": {
    "typescript": "^5.9.2",
    "@types/react": "^19.1.12",
    "@types/react-dom": "^19.1.9"
  },
  "scripts": {
    "start": "npx tsc && npx jopin ./index.js",
    "with-bunjs": "npx jopib ./index.tsx",
    "jopiWatch": "npx tsc --watch"
  },
  "type": "module"
}

Here the jopiWatch script is automatically executed by jopin if found. We use it to enable TypeScript watching.

Step 3 — The tsconfig.json file

Since we use TypeScript, we need to transform it to plain JavaScript. It’s the role of the npx tsc command in our package.json. To work correctly, this command needs a configuration file, which is why we will add a third file named tsconfig.json.

Here is the content of this file:

{
  "compilerOptions": {
    // For modern javascript features.
    "lib": ["ES2022", "DOM", "DOM.Iterable"],

    // For React component support.
    "jsx": "react",

    // For modern next.js features.
    "target": "ESNext",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",

    // For debugging typescript.
    "sourceMap": true,

    // Allow things like "import "./myFile.ts"
    // where the .ts extension is translated to .js
    //
    "rewriteRelativeImportExtensions": true,

    // Allow avoiding warning when importing CSS/image/...
    "types": ["@jopi-loader/types"],

    // Some things good to add :
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "verbatimModuleSyntax": true
  },

  // Avoid analyzing this directory,
  // which makes it much faster!
  "exclude": [ "node_modules" ]
}

Step 4 — Installing and executing

We now have a complete starting point for our tests. The only remaining step is to install the dependencies listed in the package.json file.

To do that, execute this command in a terminal:

npm install

Once done, you can execute the script:

npm run start

When opening the url http://localhost:3000 you will see a simple Hello from react! message in your browser.

Doing React SSR

We now have a sample project using Jopi Rewrite, that we will evolve to something with a full React SSR lifecycle:

  1. The server generate a HTML with the apparence of our React component.
  2. The browser load the requirements.
  3. Once loaded, he replace our fake component by the real one.

Jopi Rewrite handle all this for you. The only requirement is to tell him that you want to enable this capacity.

Step 1 — Updating index.tsx file

We will start by changing the content of the index.tsx file, in order to:

  • Server files under the www directory (it’s optional for React SSR).
  • Return our React.js component.
import {jopiApp} from "jopi-rewrite";
import React from 'react';

import MyComponent from "./MyComponent.tsx";

jopiApp.startApp(jopiEasy => {
  // We replace 'new_webSite' by 'new_fileServer'
  // which allows exposing a public directory.
  // It's not required for React SSR, but our sample need it.
  jopiEasy.new_fileServer("http://127.0.0.1:3000")
          // Our public files will be in the www directory.
          // ('www' is already the default doing
          // that you can omit this line)
          //
          .set_rootDir("www")
          .DONE_new_fileServer()

          .add_path("/")
            // When using reactResponse, your component (MyComponent)
            // is wrapped into a Page component, which allow setting
            // page title and add html headers.
            //
            .onGET(async req => req.reactResponse(<MyComponent />))
            .DONE_add_path()

          .DONE_createWebSite();
})

Step 2 — File MyComponent.jsx

We will now create a React.js component, which shows three images, each one being loading with a different mechanism.

Also we will do three other things, as a demonstration:

  • Define the page title, which will be set from inside our React component.
  • Use Tailwind CSS classes see intro
  • Use a CSS module see intro

Tailwind is supported out of the box by Jopi Rewrite. No extra config or install is required.

import React from "react";
import {mustHydrate} from "jopi-rewrite-ui";

// It's a CSS module, which allows CSS without a name conflit.
import styles from "my-component.module.css";

// Doing this build a data-url if you image is less than 10kb,
// otherwise myImage1 contains an url pointing to the image.
import myImage1 from "./image1.png?inline";

// Doing this returns the url pointing to this file.
// Jopi Rewrite automatically serves this url.
import myImage2 from "./image2.png";

// This image is at the root of the 'www' folder.
// (it's for this image that we need a file server)
const myImage3 = "http://127.0.0.1:3000/image3.png";

const Component = function() {
  // Here we set the page title.
  // 'thePage' can also be used to add headers and body properties.
  //
  const thePage = usePage();
  thePage.pageTitle = "My page title";

  // Here our classes names are Tailwind CSS classes.
  // (it's automatically enabled, nothing special to do/add)
  return <>
    <div className="grid h-56 grid-cols-3 content-center gap-4">
      <img src={myImage1} width="100" key={1}  />
      <img src={myImage2} width="100" key={1} />
      <img src={myImage3} width="100" key={1}  />
    </div>
    <div className={styles.myComponent}
         onClick={()=>alert("clicked")}>Click me!</div>
  </>
};

// mustHydrate is what let know Jopi Rewrite that the fake
// component must be replaced by the real-once once in the browser.
//
export default mustHydrate(
    import.meta, Component, 

    // This third argument is optional.
    // It allows to inline our styles into the HTML page header.
    styles
);

Step 3 — File my-component.module.css

We will now create our CSS module file. A CSS module is a CSS stylesheet, where all class names are transformed into an uniq name. Doing this allows avoiding conflict with another class name.

Here is the content of my-component.module.css. As you can guest, with red and yellow color it will be very lovely!

.my-component {
    background-color: red;
    color: yellow;
}

Testing

Our new project is now ready, and you can execute it.

What you will see here are three images. And an awful button with a red background and yellow text. It’s awful, but if you see it, then it means that the CSS is ok.

If you click on the red button, then you will have an alert.

Generated HTML

You can try to disable your browser JavaScript and see that our React.js is visually ok. The only difference will be interactivity, here they will not have interactivity.

To go further

This sample was a sample integrating most mechanisms need to do React SSR.

Jopi Rewrite packages a lot of interesting things. For example, as you can see Tailwind is supported out of the box, and if you try using Tailwind classes, then you will be happy to see that all is already configured and ready.

You can discover more features on the Jopi Rewrite website here.

You can also found other articles on Jopi Rewrite on my Dev.to page (here).

If you like this project, then thanks to star it on GitHub.
It will help this project to gain visibility and encourage his author to develop furthermore this project.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert