DEV Community

Cover image for Introducing an Alternative to NEXT.js
Shiono Yoshihide
Shiono Yoshihide

Posted on • Edited on

Introducing an Alternative to NEXT.js

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,
  };
};
Enter fullscreen mode Exit fullscreen mode

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');
});
Enter fullscreen mode Exit fullscreen mode
// views/index.jsx

export default function IndexPage{ message }) {
  return <p>{message}</p>;
}
Enter fullscreen mode Exit fullscreen mode

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 });
});
Enter fullscreen mode Exit fullscreen mode
// 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;
Enter fullscreen mode Exit fullscreen mode
// views/post.jsx

import React from 'react';

const PostPage = ({ post }) => {
  return (
    <React.Fragment>
      <p>{post.body}</p>
    </React.Fragment>
  );
};

export default PostPage;
Enter fullscreen mode Exit fullscreen mode

Is it so easy enough, right?

And we can use React as if it was a view template engine!

About react-ssr

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

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:

Usage of @react-ssr/express in JavaScript

Installation:

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

In the package.json:

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

And populate files below inside your project:

.babelrc:

{
  "presets": [
    "@react-ssr/express/babel"
  ]
}
Enter fullscreen mode Exit fullscreen mode

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');
  });
})();
Enter fullscreen mode Exit fullscreen mode

views/index.jsx:

export default function IndexPage({ message }) {
  return <p>{message}</p>;
}
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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');
  });
})();
Enter fullscreen mode Exit fullscreen mode

views/index.tsx:

interface IndexPageProps {
  message: string;
}

export default function IndexPage({ message }: IndexPageProps) {
  return <p>{message}</p>;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

In the package.json:

{
  "scripts": {
    "start": "ts-node --project tsconfig.server.json server/main.ts"
  }
}
Enter fullscreen mode Exit fullscreen mode

And then, populate files below inside your project:

.babelrc:

{
  "presets": [
    "@react-ssr/nestjs-express/babel"
  ]
}
Enter fullscreen mode Exit fullscreen mode

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"
  ]
}
Enter fullscreen mode Exit fullscreen mode

tsconfig.server.json:

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "commonjs"
  },
  "include": [
    "server"
  ]
}
Enter fullscreen mode Exit fullscreen mode

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`);
  });
})();
Enter fullscreen mode Exit fullscreen mode

server/app.module.ts:

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

@Module({
  controllers: [
    AppController,
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

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 };
  }
}
Enter fullscreen mode Exit fullscreen mode

Finally, views/index.tsx:

interface IndexPageProps {
  user: any;
}

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

export default IndexPage;
Enter fullscreen mode Exit fullscreen mode

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

There Are Many Examples

examples/with-jsx-antd

examples/with-jsx-antd

examples/with-jsx-emotion

examples/with-jsx-emotion

examples/with-jsx-material-ui

examples/with-jsx-material-ui

examples/with-jsx-semantic-ui

examples/with-jsx-semantic-ui

examples/with-jsx-styled-components

examples/with-jsx-styled-components

Conclusion

Please try react-ssr and send us feedbacks!

Best,

Top comments (25)

Collapse
 
adam_cyclones profile image
Adam Crockett 🌀

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.

Collapse
 
saltyshiomix profile image
Shiono Yoshihide

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!

Collapse
 
adam_cyclones profile image
Adam Crockett 🌀

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.

Thread Thread
 
saltyshiomix profile image
Shiono Yoshihide

Thank you letting me know!

It may be similar to react-native-dom, right? (If wrong, I'm sorry.)

I will try puppeteer later :)

Thread Thread
 
adam_cyclones profile image
Adam Crockett 🌀

Potentially? I am not a react specialist. Puppeteer is fantasticly useful, I have used it within a unit testing framework to create acceptance tests.

Collapse
 
bbarbour profile image
Brian Barbour

Does it rehydrate the JS on the client side?

Collapse
 
saltyshiomix profile image
Shiono Yoshihide • Edited

Thank you for your comment!

It hydrates id="react-ssr-root" once :)

Collapse
 
mxrcochxvez profile image
Marco Chavez

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.

Collapse
 
bbarbour profile image
Brian Barbour

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.

Thread Thread
 
saltyshiomix profile image
Shiono Yoshihide

Thank you for your explanation!

In this point, react-ssr rehydrates DOM so we can use full React features like hooks :)

Collapse
 
saltyshiomix profile image
Shiono Yoshihide

I'm interested in the meaning of rehydrate, too.

Anyway, react-ssr hydrates a DOM target once :)

Collapse
 
lawrencejohnson profile image
Lawrence • Edited

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.

Collapse
 
saltyshiomix profile image
Shiono Yoshihide

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!

Collapse
 
wnemencha profile image
William Nemencha

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?

Collapse
 
saltyshiomix profile image
Shiono Yoshihide

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:

  • Simple Blogging Site
  • Documentation Site
  • Test React without a webpack config
Collapse
 
jcarlosweb profile image
Carlos Campos

Another option for backend is tsed.io/

Collapse
 
dance2die profile image
Sung M. Kim

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? 😉

Collapse
 
saltyshiomix profile image
Shiono Yoshihide

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.

Collapse
 
dance2die profile image
Sung M. Kim

You've created something fun "accidentally" in a good way :)

Collapse
 
saurabhdaware profile image
Saurabh Daware 🌻

This is so cool! thank you for sharing and great project 🦄

Collapse
 
saltyshiomix profile image
Shiono Yoshihide

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 :)

Collapse
 
joshuaamaju profile image
Joshua Amaju

🙌🏾🙌🏾🙌🏾. You're a life saver. Been looking for something like this, almost gave to start something similar on my own.

Collapse
 
mxrcochxvez profile image
Marco Chavez

What a great article covering an amazing new technology. I am going to give this a try today!

Collapse
 
saltyshiomix profile image
Shiono Yoshihide

Thank you very much, Macro!

Please check out this "good old way" server rendered view technology :)

Collapse
 
kumard3 profile image
Kumar Deepanshu

Bro I have seen your nextron project and it is dope, can you tell me how you make a cli tool, create-nextron-app .