For the server side, the BrowserRouter will not work and thus StaticRouter is to be used as per documentation. I am doing same but I am still getting the error. Following is my setup
Invariant Violation: Browser history needs a DOM
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter as Router } from 'react-router';
// import our main App component
import App from '../src/App';
const path = require('path');
const fs = require('fs');
export default (req, res) => {
// get the html file created by CRA's build tool
console.log('url : ', req.baseUrl);
const filePath = path.resolve(__dirname, '..', '..', 'build', 'index.html');
fs.readFile(filePath, 'utf8', (err, htmlData) => {
if (err) {
console.error('err', err);
return res.status(404).end();
}
// render the app as a string
const html = ReactDOMServer
.renderToString(<Router location={req.baseUrl}>
<App />
</Router>);
// now inject the rendered app into our html and send it
return res.send(htmlData
.replace('<div id="root"></div>', `<div id="root">${html}</div>`));
});
};
Related
I have created an app using create-react-app with a basename '/admin' and it is working just fine in development mode. All routes working properly both on localhost and behind nginx proxy.
When I build the app using npm run build I get a blank screen on the '/admin' url, with the following errors in the console:
The script from “https://192.168.1.2/admin/static/js/main.49bb4878.js”
was loaded even though its MIME type (“text/html”) is not a valid
JavaScript MIME type.
The stylesheet https://192.168.1.2/admin/static/css/main.4efb37a3.css
was not loaded because its MIME type, “text/html”, is not “text/css”.
Uncaught SyntaxError: expected expression, got '<' main.49bb4878.js:1
I have tried both <BrowserRouter pathname="/admin">...</BrowserRouter> and the one I have in the following index.js file.
It seems like the server sends the index.html file no matter what the client requests...
This is my index.js file:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import { configureStore } from '#reduxjs/toolkit';
import storeToConfigure from './configureStore';
import CustomRouter from './utils/CustomRouter';
import * as buffer from 'buffer';
import { BrowserRouter } from 'react-router-dom';
console.log(window);
window.Buffer = buffer;
window.process = {}
export const store = configureStore(storeToConfigure);
export const history = createBrowserHistory({ basename: '/admin' });
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<CustomRouter history={history} basename="/thug-app">
<ScrollToTop>
<App />
</ScrollToTop>
</CustomRouter>
</Provider>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint.
reportWebVitals(console.log);
This is the CustomRouter I'm using, in order to be able to directly access the history anywhere without a hook:
import React, { useLayoutEffect, useState } from 'react';
import { Router } from 'react-router-dom';
const CustomRouter = ({
basename,
children,
history,
}) => {
const [state, setState] = useState({
action: history.action,
location: history.location,
});
useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
};
export default CustomRouter;
Again, everything works just fine in development. The problem is when I build the app for production. I have tried both pm2 and serve packages (same names on npmjs). The serve package returns 404, while pm2 returns the errors I mention above.
Thank you for taking the time to help!
I managed to make it work with this workaround:
I ditched both pm2 and serve and used a custom express.js server.
The setup:
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
// app.use(express.static(path.join(__dirname, 'build')));
app.get('/*', (req, res) => {
let theUrl = req.originalUrl.replace('/admin', '');
if (!!!theUrl) {
theUrl = 'index.html';
}
try {
const target = path.join(__dirname, 'build', theUrl);
console.log(target);
if (fs.statSync(target)) {
res.sendFile(target);
} else {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
}
} catch (error) {
console.log(error);
res.sendFile(path.join(__dirname, 'build', 'index.html'));
}
});
app.listen(4000, () => console.log('admin app listening on 4000'));
It also seems to work fine without the following line:
app.use(express.static(path.join(__dirname, 'build')));
Like I had noticed, both pm2 and serve sent the index.html file no matter what the client requested.
Seems to be working fine behind nginx proxy as well.
Whenever the requested file does not exist, simply serve the index.js file.
I was having a problem with SEO in my react app so I did Server-side rendering. At first, I changed '.render' to '.hydrate' in src/index.js. I created a server folder in it server.js and index.js. But it's still showing only the script tag with 'root' in the source code in the browser after deploying to firebase.
src/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import "bootstrap/dist/css/bootstrap.css";
import { hydrateRoot } from "react-dom/client";
const container = document.getElementById("root");
const root = hydrateRoot(container, <App />);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
server.js
import path from "path";
import fs from "fs";
import express from "express";
import React from "react";
import ReactDOMServer from "react-dom/server";
import App from "../src/App";
const PORT = 3000;
const app = express();
const router = express.Router();
const serverRenderer = (req, res, next) => {
fs.readFile(path.resolve("./build/index.html"), "utf8", (err, data) => {
if (err) {
console.error(err);
return res.status(500).send("An error occurred");
}
return res.send(
data.replace(
'<div id="root"></div>',
`<div id="root">${ReactDOMServer.renderToString(<App />)}</div>`
)
);
});
};
router.use("^/$", serverRenderer);
router.use(
express.static(path.resolve(__dirname, "..", "build"), { maxAge: "30d" })
);
// tell the app to use the above rules
app.use(router);
// app.use(express.static('./build'))
app.listen(PORT, () => {
console.log(`SSR running on port ${PORT}`);
});
index.js
require("ignore-styles");
require("#babel/register")({
ignore: [/(node_modules)/],
presets: ["#babel/preset-env", "#babel/preset-react"],
});
require("./server");
It is working when I'm running 'node server/server.js' in the terminal but when I'm running 'npm start' it's not working or when I'm deploying it to firebase with 'firebase deploy' after 'npm run build'.
Thank you.
I used create-react-app to create a React project. I was experimenting with server-side rendering on express servers.
I found a code snippet that showed me how to do it.
ReactDOM.hydrate(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
This is my server side code.
index.js
require("ignore-styles");
require("#babel/register")({
ignore: [/(node_modules)/],
presets: ["#babel/preset-env", "#babel/preset-react"],
});
require("./server");
server.js
import path from "path";
import fs from "fs";
import express from "express";
import React from "react";
import ReactDOMServer from "react-dom/server";
import App from "../src/App";
const PORT = 8080;
const app = express();
const router = express.Router();
const serverRenderer = (req, res, next) => {
fs.readFile(path.resolve("./build/index.html"), "utf8", (err, data) => {
if (err) {
console.error(err);
return res.status(500).send("An error occurred");
}
return res.send(
data.replace(
'<div id="root"></div>',
`<div id="root">${ReactDOMServer.renderToString(<App />)}</div>`
)
);
});
};
router.use("^/$", serverRenderer);
router.use(
express.static(path.resolve(__dirname, "..", "build"), { maxAge: "30d" })
);
// tell the app to use the above rules
app.use(router);
// app.use(express.static('./build'))
app.listen(PORT, () => {
console.log(`SSR running on port ${PORT}`);
});
It works fine when I try to get text or other data; however, when I try to render images from the server side, they do not appear.
I tried every other solution I could think of, but nothing seemed to work.
images are not rendering
and also not showing in View Page Source
enter image description here
we also tried <img src={require("./logo.svg")} className="App-logo" alt="logo" />
I tried all the solutions from stackoverflow as well as other sources, but its not working.
I was expecting images to be rendered properly on page.
I'm trying to use react, serverless and SSR please don't ask me why I don't use next.js, anyway.
i got the next error ERROR in unable to locate '/home/franklinserif/proyects/ssr-develop/build' glob
here is my server index.js code
// index.js
import serverless from "serverless-http";
import express from "express";
import path from "path";
// import middleware
import renderer from "./middleware/renderer";
const app = express();
// root (/) should always serve our server rendered page
app.use("^/$", renderer);
// serve static assets
console.log(path.join(__dirname, "./client", "./build"));
app.use(express.static(path.join(__dirname, "./client", "./build")));
// handler
export const handler = serverless(app);
and this is my renderer file
// renderer.js
import fs from "fs";
import path from "path";
import React from "react";
import ReactDOMServer from "react-dom/server";
// import main App component
import App from "../client/src/App";
export default (req, res, next) => {
// point build index.html
console.log(path.resolve("../client", "./build", "index.html"));
const filePath = path.resolve("../client", "./build", "index.html");
// read in html file
fs.readFile(filePath, "utf8", (err, htmlData) => {
if (err) {
return res.send(err).end();
}
// render the app as a string
const html = ReactDOMServer.renderToString(<App />);
// inject the rendered app into our html and send it
return res.send(
// replace default html with rendered html
htmlData.replace('<div id="root"></div>', `<div id="root">${html}</div>`)
);
});
};
I don't know why I can find the build folder
There are already questions around this topic with solutions pointing to memoryHistory on server. However, I did that and the problem still remains to be on store.js which is inside client folder. Although I am not using this client/store.js it still gives me same error as the subject.
So my guess is there is something wrong in the way I am wrapping up my component on server or client side.
I am working on already loaded project stack - redux, react-redux, react-router-redux, redux-thunk, history etc... and these are honestly daunting for me to make an addition for a setup for SSR -server side rendering (my basic motive)
I will share my structure and important files which are involved in this exercise !
Let's welcome the server first.
server/bootstrap.js
require('ignore-styles');
require('babel-register')({
ignore: [ /(node_modules)/ ],
presets: ['es2015', 'react-app']
});
require('./index');
server/index.js
import express from 'express';
import serverRenderer from './middleware/renderer';
const PORT = 3000;
const path = require('path');
const app = express();
const router = express.Router();
// root (/) should always serve our server rendered page
router.use('^/$', serverRenderer);
// other static resources should just be served as they are
router.use(express.static(
path.resolve(__dirname, '..', 'build'),
{ maxAge: '30d' },
));
router.use('*', serverRenderer);
// tell the app to use the above rules
app.use(router);
// start the app
app.listen(PORT, (error) => {
if (error) {
return console.log('something bad happened', error);
}
console.log("listening on " + PORT + "...");
});
server/middleware/renderer.js
import React from 'react'
import ReactDOMServer from 'react-dom/server';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import store from '../../src/store';
import {createMemoryHistory } from 'history';
import { StaticRouter } from 'react-router'
// import our main App component
import App from '../../src/index';
const path = require("path");
const fs = require("fs");
const history = createMemoryHistory({
initialEntries: ['/', '/next', '/last'],
initialIndex: 0
})
export default (req, res, next) => {
const filePath = path.resolve(__dirname, '..', '..', 'build', 'index.html');
fs.readFile(filePath, 'utf8', (err, htmlData) => {
if (err) {
console.error('err', err);
return res.status(404).end()
}
const html = ReactDOMServer.renderToString(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>
);
return res.send(
htmlData.replace(
'<div id="root"></div>',
`<div id="root">${html}</div>`
)
);
});
}
My src folder structure will be
components
containers
app
index.js
styles.js
index.js
store.js
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import { StaticRouter } from 'react-router'
import './index.css';
import store, { history } from './store';
import App from './containers/app';
const target = document.querySelector('#root');
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
target
);
The tough nut to understand
src/store.js
import { createStore, applyMiddleware, compose } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import thunk from 'redux-thunk';
import createHistory from 'history/createBrowserHistory';
import rootReducer from './reducers';
export const history = createHistory();
const initialState = {};
const enhancers = [];
const middleware = [thunk, routerMiddleware(history)];
if (process.env.NODE_ENV === 'development') {
const devToolsExtension = window.devToolsExtension;
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension());
}
}
const composedEnhancers = compose(applyMiddleware(...middleware), ...enhancers);
const store = createStore(rootReducer, initialState, composedEnhancers);
export default store;
on running node server/bootstrap.js, it give me an error
Invariant Violation: Browser history needs a DOM and this error generates in store.js
If someone could please let me know by the code shared what and where I am doing wrong to my current create-react-app for SSR!
I was loading the App twice. Once in client and other in server.
I rendered in server alone with the same setup (Provider and connected router) and importantly changed the createBrowserHistory to createMemoryHistory and it worked.