loading...
Cover image for Introducing an Alternative to NEXT.js

Introducing an Alternative to NEXT.js

saltyshiomix profile image Shiono Yoshihide Updated on ・6 min read

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

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

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:

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

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,

Posted on by:

Discussion

markdown guide
 

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.

 

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!

 

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

 

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:

  • Simple Blogging Site
  • Documentation Site
  • Test React without a webpack config
 

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

 

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