ReactJS Serverside rendering with Node - node.js

I have followed the tutorial found here:
https://blog.frankdejonge.nl/rendering-reactjs-templates-server-side/
In my server.js:
'use strict';
require("babel/register");
var React = require('react');
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.json());
app.use('/', function(req, res) {
try {
var view = path.resolve('public/src/' + req.query.module);
var component = require(view);
var props = req.body || null;
res.status(200).send(
React.renderToString(
React.createElement(component, props)
)
);
} catch (err) {
res.status(500).send(err.message);
}
});
app.listen(3000);
console.log('Listening carefully...')
But when I run it I get Cannot find module 'babel/register'
If I comment that out, it works, but I get the following in the browser:
Unexpected token import
I'm guessing this is due to the error.
How can I fix this?
I changed it to this:
require('babel-register')({
presets: ['es2015', 'react']
});
...
Which got it a bit further, but now in my browser I am getting:
React.renderToString is not a function
My component:
import React from 'react';
class HelloComponent extends React.Component {
render () {
return (
<h1>Hello, {this.props.name}!</h1>
);
}
}
HelloComponent.defaultProps = { name: 'World' };
export default HelloComponent;

Looks like this code is using BabelJS version 5 - so when you will install babel#5 - it should work.
But maybe it would be better if you replace require("babel/register"); with require("babel-register"); and use babel#6. Also, you will need to add .babelrc file with configuration for babeljs (https://babeljs.io/docs/usage/babelrc/).
If you are looking to ready to use configuration for server-side rendering for react components - take a look at this project: https://github.com/zxbodya/reactive-widgets (I am an author).
It is a bit more than just server side rendering - it is a complete solution that will allow you to build isomorphic js components for your PHP application.

The proper syntax for babel register seems different now: use require("babel-register"); after having installed babel.
see require('babel/register') doesn't work : it is a similar issue

Related

require is not define / module.exports is not define with node.js and Express

After receveing help about using Express, I continued to follow tutorials about Node.js. I'm at a point where i'm building my own routes in controllers to create a REST API. I have two files, app.js and /controllers/account-api.js.
Here's my app.js shortened (i deleted the parts that were not used my my test), and the line that is returning me some issues.
import express from 'express';
import exphbs from 'express-handlebars';
import * as accountApiRoutes from './controllers/account-controller.js';
import * as bodyParser from 'body-parser';
var app = express();
app.engine('handlebars', exphbs());
app.set('view engine', 'handlebars');
app.use(bodyParser.urlencoded({extended:true}));
app.use(accountApiRoutes); // ISSUE HERE, i tried ('/account-api', accountApiRoutes) too
app.get('/', function(req, res)
{
res.redirect('/server-home');
});
app.get('/server-home', function(req, res)
{
res.render('server-home');
});
app.listen(1337, '127.0.0.1');
console.log('Express Server running at http://127.0.0.1:1337/');
And here's my ./controllers/account-api.js shortened again to give the main elements that causes the issue :
import express from 'express';
const apiRouter = express.Router(); //ISSUE HERE
var accounts = [];
accounts.push( { code: 1, name: 'Pierrette', adress: 'Sur la Lune'} );
// =========== API ROUTES =========== //
// GET
apiRouter.route('/produit-api/produit/:code')
.get( function(req, res, next) {
var codeSended = req.params.code;
var account = findAccountInArrayByCode(codeSended);
res.send(account);
});
// =========== METHODS AND FUNCTIONS =========== //
// GET
function findAllAccounts() {
return accounts;
}
function findAccountInArrayByCode(codeSended) {
var accountFound = null;
for(i in accounts)
{
if(accounts[i].code === codeSended)
{
accountFound = accounts[i];
break;
}
}
return accountFound;
}
module.exports = { //ISSUE HERE
getApiRouter: function() {
const apiRouteur = express.Router();
return apiRouter;
}
}
The problem is.. This code returns me "module" is not defined.
I use Node.JS with Express and Handlebars.
For what I saw online, when using "app.use", it requires a function. And module.exports too. I tried various solutions, like this one :
account-api.js
const apiRouter = function() { return express.Router() }
...
module.exports = apiRouteur;
The problem is that it changes the type of apiRouteur, when calling apiRouteur.get from IRouter to () => Router, and the routes break.
I don't know how to arrange the code to make the module.exports returning a function that works, or if the problem is not even about the type of value returned, but if I'm missing dependancies, etc...
Thanks for your help.
EDIT : With the explanations I got, I replaced all my ES6 calls to commonjs imports. But it doesn't solve the problem. Now it's "require" that's not define.
I was stuck firstly by "require is not defined", and the solution I was given by reading old SO threads about it, the answer was regularly to use ES6 imports...
ack to the begining I guess ! Maybe I miss something in my project?
Your problem is this line app.use(accountApiRoutes); and you are using a mix of ES6 and commonjs modules.
To fix the module imports (as you are using .js files not .mjs) change all your ES6 imports i.e import * as xyz imports to commonjs imports const x = require('...');
The accountApiRoutes is an object but not a Router object.
To fix you just need to pass the router object to the app.use function.
So you will need to make a couple of changes based on what you have supplied above.
// ./controllers/account-api.js
const express = require('express');
...
module.exports = { //ISSUE HERE
getApiRouter: function() {
return apiRouter; // you have already defined the router you don't need to recreate it
}
}
Properly pass the Router object to the express app.
const express = require('express');
const exphbs = require('express-handlebars');
const bodyParser = require('body-parser');
const accountApiRoutes = require('./controllers/account-controller.js');
...
app.use(accountApiRoutes.getApiRouter());
You could also just set module.exports to your configured router in your account-api.js and then you could pass it directly to app.use as you have already done in your server above. Either way should work. To can do that as follows:
// ./controllers/account-api.js
const express = require('express');
...
module.exports = apiRouter;
And in your server.js
const accountRouter = require('./controllers/account-controller.js');
app.use(accountRouter);

"window is not defined" in Angular service yet code works perfectly

I'm using indexeddb in an Angular 8 service and need window. The code builds without errors and the app creates the db objectstore flawlessly. But at runtime in production mode (with an actual node server instead of ng serve where this error does not occur), I get this error in the terminal running angular:
ERROR ReferenceError: window is not defined
at IndexedDBService.isSupported (D:\MartijnFiles\Documents\Programming\Fenego\fenego-labs-angular\dist\server.js:71199:9)
at IndexedDBService.openDB (D:\MartijnFiles\Documents\Programming\Fenego\fenego-labs-angular\dist\server.js:71203:18)
at Promise (D:\MartijnFiles\Documents\Programming\Fenego\fenego-labs-angular\dist\server.js:72026:46)
Again, it all works and the isSupported() function would stop openDB() from being run if window was actually undefined. There is also no error in the browser console.
Here is the relevant part of my service.
#Injectable()
export class IndexedDBService {
isSupported(): boolean {
return !!window.indexedDB;
}
openDB(dbName: string,
version: number,
onUpgradeNeededCallback: OnUpgradeNeededCallback,
onSuccessCallback: OnOpenSuccessCallback,
onErrorCallback: OnOpenErrorCallback,
onBlockedCallback: OnOpenBlockedCallback): Observable<IDBOpenDBRequest> {
let openDBRequest: IDBOpenDBRequest = null;
if (this.isSupported()) {
openDBRequest = window.indexedDB.open(dbName, version);
openDBRequest.onupgradeneeded = onUpgradeNeededCallback;
openDBRequest.onsuccess = onSuccessCallback;
openDBRequest.onerror = onErrorCallback;
openDBRequest.onblocked = onBlockedCallback;
}
return of(openDBRequest);
}
There are many suggest "solutions" out there that mostly boil down to providing it via a service or plain injection (eg. point 1 in this blog https://willtaylor.blog/angular-universal-gotchas/) but all it does is pass window from some other service via injection to mine. But my code works so it clearly has access to window...
Update:
The following line in a component's ngOnInit() has the same problem with Worker being "not defined" yet the worker is loaded and runs perfectly:
const offlineProductsWorker = new Worker('webworkers/offline-products-worker.js');
Update2:
I have found a solution (posted below) but checking for server side rendering seems more like a workaround than solving the fact that server side rendering is happening (not sure if that is supposed to be the case).
I will include my server.ts script that I use with webpack below. It is a modification of one from another project and I don't understand most of it. If anyone can point out to me what I could change to stop the server side rendering, that would be great. Or, if it is supposed to do that then why?
// tslint:disable:ish-ordered-imports no-console
import 'reflect-metadata';
import 'zone.js/dist/zone-node';
import { enableProdMode } from '#angular/core';
import * as express from 'express';
import { join } from 'path';
import * as https from 'https';
import * as fs from 'fs';
/*
* Load config from .env file
*/
require('dotenv').config({ path: './ng-exp/.env' });
const IS_HTTPS = process.env.IS_HTTPS === 'true';
const SSL_PATH = process.env.SSL_PATH;
const ENVIRONMENT = process.env.ENVIRONMENT;
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
const logging = !!process.env.LOGGING;
// Express server
const app = express();
const PORT = process.env.PORT || 4200;
const DIST_FOLDER = process.cwd();
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main');
// Express Engine
import { ngExpressEngine } from '#nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '#nguniversal/module-map-ngfactory-loader';
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/modules/express-engine)
app.engine(
'html',
ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [provideModuleMap(LAZY_MODULE_MAP)],
})
);
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'ng-exp'));
// Server static files from /browser
app.get(
'*.*',
express.static(join(DIST_FOLDER, 'ng-exp'), {
setHeaders: (res, path) => {
if (/\.[0-9a-f]{20,}\./.test(path)) {
// file was output-hashed -> 1y
res.set('Cache-Control', 'public, max-age=31557600');
} else {
// file should be re-checked more frequently -> 5m
res.set('Cache-Control', 'public, max-age=300');
}
},
})
);
// ALl regular routes use the Universal engine
app.get('*', (req: express.Request, res: express.Response) => {
if (logging) {
console.log(`GET ${req.url}`);
}
res.render(
'index',
{
req,
res,
},
(err: Error, html: string) => {
res.status(html ? res.statusCode : 500).send(html || err.message);
if (logging) {
console.log(`RES ${res.statusCode} ${req.url}`);
if (err) {
console.log(err);
}
}
}
);
});
const sslOptions = {
key: fs.readFileSync(`${SSL_PATH}/${ENVIRONMENT}/server.key`),
cert: fs.readFileSync(`${SSL_PATH}/${ENVIRONMENT}/server.crt`),
};
// Start up the Node server
let server;
if (IS_HTTPS) {
server = https.createServer(sslOptions, app);
} else {
server = app;
}
server.listen(PORT, () => {
console.log(`Node Express server listening on http${IS_HTTPS ? 's' : ''}://localhost:${PORT}`);
const icmBaseUrl = process.env.ICM_BASE_URL;
if (icmBaseUrl) {
console.log('ICM_BASE_URL is', icmBaseUrl);
}
});
There is a related issue here:
https://github.com/hellosign/hellosign-embedded/issues/107
Basically, to avoid the error you can declare somewhere globally the window.
if (typeof window === 'undefined') {
global.window = {}
}
I also found React JS Server side issue - window not found which explains the issue better and why it works on the client side.
I found the solution thanks to some input from ChrisY
I deploy my code using webpack and run it using node. It seems that node somehow renders it server side and then the browser renders it too. The server site portion has no effect on the storefront but does cause the (seemingly harmless) error. In isSupported() I added console.log(isPlatformBrowser(this.platformId))and it printed false in the server terminal but true in the browser. Thus, I changed the code as follows:
constructor(#Inject(PLATFORM_ID) private platformId: any) {}
isSupported(): boolean {
return isPlatformBrowser(this.platformId) && !!indexedDB;
}
Now it still works in the browser as it did before but there is no server error.
Update:
I have also found the cause for the server side rendering. The server.ts file in the description has a block with res.render(. This first renders the page on the server and if it does not receive html, it returns status code 500. Otherwise it allows the client to render it. Seeing as this is a realistic scenario, I have decided to keep the extra isPlatformBrowser(this.platformId) check in my code. This should then be done for anything that can only be performed by the client (window, dom, workers, etc.).
Not not have server side rendering, an alternative to the res.render( block is
res.status(200).sendFile(`/`, {root: join(DIST_FOLDER, 'ng-exp')});

I tried implementing server-side rendering, but broke my API in the process

I have very basic application at the moment that I wanted to try to implement SSR with.
problem 1
Previously I would send index.html (that had a script tag with my bundle.js inside) to requests made to '/' and that would render on the client.
Now I'm rendering the app on the server for every request made to '/' and now, expectantly, when I make GET requests to '/api/users/' my isomorphic rendering function is executed (even though this is an API request and should not be rendering anything).
I understand why, because of where I placed this function in the middleware is such that all requests made to the app will run that middleware.
My question is: How can I ignore this function unless a browser is requesting it? Is that even the right question? It sounds like I'm asking for a hack, which means I'm misunderstanding something.
problem 2
This is the cause of another problem I am having when the client requests bundle.js. Because each request is rendering the app and sending html back, requests to get the bundle are receiving an HTML response, which when parsed by the browser errors out as expected (Unexpected token '<').
I think I've designed my server poorly, and maybe someone can give me some insight or point me to a good place to start learning.
server.js
require('babel-register')
var express = require('express')
// TODO setup global error handling
var path = require('path') // built-in middleware
var mongoose = require('mongoose')
const isomorphic = require('./isomorphic')
// change promise library of mongoose to bluebird
mongoose.Promise = require('bluebird')
// create the api and auth
var api = require('./api/api')
var auth = require('./auth/routes')
// make app
var app = express()
// setup the app middleware
require('./middleware/appMiddleware')(app)
// get the api and auth
app.use('/api', api)
app.use('/auth', auth)
// serve static assets
app.use(express.static('dist'))
// serverside rendering
app.use(isomorphic)
// give the bundle
// app.get('/dist/bundle.js', function(req, res) {
// res.sendFile(path.resolve('dist/bundle.js'), function(err) {
// if (err) {
// res.status(500).send(err)
// }
// })
// })
module.exports = app
Note app.get('/dist/bundle.js', function... is commented out because I believe app.use(express.static('dist)) should be handling that. Right?
isomorphic.js
var React = require('react')
var ReactDOMServer = require('react-dom/server')
var StaticRouter = require('react-router-dom').StaticRouter
var ServerStyleSheet = require('styled-components').ServerStyleSheet
var fs = require('fs')
var _ = require('lodash')
var baseTemplate = fs.readFileSync('./index.html') // TODO change to async function maybe
var template = _.template(baseTemplate)
var sheet = new ServerStyleSheet()
var something = sheet.collectStyles(App)
var css = sheet.getStyleTags()
var App = require('../src/js/App').default
const isomorphic = (req, res) => {
var context = {}
var body = ReactDOMServer.renderToString(
React.createElement(StaticRouter, { location: req.url, context },
React.createElement(App)
)
)
console.log('hello?');
if (context.url) {
// TODO if there is a redirect
}
res.write(template({body: body}))
res.end()
}
module.exports = isomorphic
Please let me know if you need to see any other files.
update 1
I added a check in isomorphic.js that looks like this:
if (req.url !== '/') {
next()
} else {
...
}
And I uncommented the app.get('/dist/bundle.js') code.
Now everything seems to be half working, however:
That if check seems to be bad practice because the user could request routes that exist on the client that are not the root.
React's server render does match the checksum on the client, therefore negating the SSR.
And the app.use(express.static('dist')) appears to be doing absolutely nothing. The 'dist' directory is one level up from my server.js file, so I've changed it to '../dist' but it still 404s (with that .get commented).
update 2
I have figured out why express.static('dist') was not working.
One, I changed it from express.static('dist') to express.static(path.join(__dirname, '../dist/')) to be sure it going to the correct path.
Two, in my index.html, the script tag was originally
<script src="dist/bundle.js"></script>
when apparently it should have been this (I got lucky and guessed this could have been the issue)
<script src="bundle.js"></script>

How to export Component for server side rendering in React

All:
I am pretty new to React, right now I am trying how to do server side rendering, I use Express.js as my server, so the code is like:
//server.js
var express = require("express");
var ReactDOMServer = require("react-dom/server");
var MyCom = require("./components");
var domstring = ReactDOMServer.renderToString(MyCom);
var app = express();
app.get("/", function(req, res){
res.json({
name: "new com",
dom: domstring
});
});
And
// components.js
var React = require("react");
var MyCom = React.createClass({
render: function(){
return (<h1>Hello, server side react</h1>);
}
});
module.exports = MyCom;
I use babel to transpile the JSX, but when I start server, I do not know why I keep getting error like:
Invariant Violation: renderToString(): You must pass a valid
ReactElement.
Could anyone give some clue why this not work?
Thanks
Your module exports a ReactComponent, and renderToString accepts a ReactElement (i.e. an instantiated ReactComponent).
In order to render it, you want to instantiate it like so:
ReactDOMServer.renderToString(<MyCom />);
Using a factory allows you to have all your components in separate files and instantiate them without using jsx syntax in your server. Very useful for the main wrapper component.
require('babel-core/register')({
presets: ['react']
});
var express = require('express');
var reactDOM = require('react-dom/server');
var react = require('react');
var app = express();
app.get('/', function (req, res) {
var mainFile = require('./app.jsx');
var output = reactDOM.renderToString(react.createFactory(mainFile)({
data: yourInitialData
}));
res.send(output);
});

Isomorphic React and JSX - rendering component as string on server

I'm trying to render an extremely simple component on the server before passing to the client, transforming using gulp and babelify like so:
gulp.task("react-assessment", function(){
return browserify("./app/assessment/react/components/app.react.js")
.transform(babelify)
.bundle()
.pipe(source("reactBundle.js"))
.pipe(gulp.dest("./browser"))
});
The component works fine on the client:
var React = require("react");
var ReactDOM = require("react-dom");
var Title = React.createClass({
render: function(){
return <h1>Hello World</h1>
}
});
module.exports = ReactDOM.render(
<Title/>,
document.getElementById("react-assessment")
);
However when I require the file in Node.js with Express, the server crashes with unexpected token
return <h1>Hello World</h1>
^
SyntaxError: Unexpected token <
When I've used the old methodology of using /** #jsx React.DOM */ at the top of the component file, there were no problems.
Route:
var express = require('express'),
router = express.Router(),
ReactDOMServer = require("react-dom/server");
JSX = require('node-jsx').install({
extension: '.jsx'
}),
AssessmentComponent = require("../react/components/app.react.jsx");
Where am I going wrong?
Solved by using babel/register. No need for React.DOM

Resources