I have an app where I configured server side rendering. Everything is working nice and my component is rendered on the server. The problem is that I get my component rendered twice on the screen. One comes from <div id="content"><%- content %></div>that I am using for server rendering and one comes from <script src="http://localhost:3001/bundle.js"></script>. I use webpack to make two bundles for my server and client. Why is this happening and how can I fix this?
views/index.ejs
<body>
<div id="app"></div>
<div id="content"><%- content %></div>
<script src="http://localhost:3001/bundle.js"></script>
</body>
index.js
app.use(Express.static(path.join(__dirname, '../', 'dist')))
app.use(serverRenderer)
app.get('*', (req: Object, res: Object) => {
res.render('index', {content: req.body})
})
serverRender
import React from 'react'
import ReactDOM from 'react-dom/server'
import { match, RouterContext } from 'react-router'
import routes from '../client/routes.js'
async function render (component) {
const content = ReactDOM.renderToString(component)
return content
}
async function getMatchParams (routes, currentUrl) {
return new Promise((resolve, reject) => {
match({routes: routes, location: currentUrl}, (err, redirect, props) => {
if (err) {
return reject(err)
}
return resolve(props)
})
})
}
export default async(req, res, next) => {
const renderProps = await getMatchParams(routes, req.url)
if (renderProps) {
const component = (
<RouterContext {...renderProps} />
)
req.body = await render(component)
next()
}
}
Ok. I have found a problem. I was referring to the bundle and server rendered string with two separate <div>. Inside my app.js I was doing this
render(
<Router history={browserHistory}>
{routes}
</Router>,
document.getElementById('app')
)
Thats why I should have been sending the string to the template like this.
app.use(Express.static(path.join(__dirname, '../', 'dist')))
app.use(serverRenderer)
app.get('*', (req: Object, res: Object) => {
res.render('index', {app: req.body})
})
And finally my views/index.js should look like this
<body>
<div id="app"><%- app %></div>
<script src="http://localhost:3001/bundle.js"></script>
</body>
I also faced that problem and found a solution.
On package.json,
"start": "npm-run-all --parallel dev:*",
It will run webpack and node build/bundle.js.
Then 2 things happened simultaneously, webpack build project, node build/bundle.js
After webpack built project, then node build/bundle.js runs again since bundle.js is changed.
So there was twice calls on both server and client side. I solved this problem very easily.
First run npm run build, then run node build/bunde.js . Then it will run everything once :)
Related
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.
simplifying my post:
my ssr webpage blinks when starting client which means page renders server side rendered html then goes blank and then it starts loading everything all over again.
going through the details:
i'm working on a react project which we decided to change it from being rendered in client to render in server. the project includes react-router-dom ,redux and react-redux ,material-ui which comes with react-jss ,loadable/component ,also handling the head elements by react-helmet-async ,and in ssr it's using express.js which seems to be a must.
for react-router-dom i did everything that is on the docs. using BrowserRouter in client and StaticRouter in ssr and passing a context object to it.
for redux and react-redux i saved preloaded_state in a variable in window and fetched it client side then pass it to store.also fetched some external data to get the content on the source of the page.so i have some requests and data fetching in ssr.
for material-ui i created a new serverSideStyleSheet and collected all the styles from all over the project.
for react-helmet-async i've got different Helmet tags for each page that collects different title ,description and ... individualy.there is also a helmetProvider wrapper for csr and ssr.
at first i used react-helmet but it wasn't compatible with renderToNodeStream.i didn't change react-helmet-async although i'm not using renderToNodeStream but kept it to migrate to renderToNodeStream one day hopefully.
about express.js i'm using it as the docs say but after i added loadable/component i wasn't able to get a successful server side rendering by just adding app.get('*' , ServerSideRender) .so i had to add every url i wanted to render in server in app.get(url ,ServerSideRender).
the other thing about the project is that i didn't eject and created it with create-react-app and there is no webpack config or babelrc but instead i'm using craco.config.js
the last thing is that i've excluded index.html from ssr and instead i've made the tags myself in SSR.js file so index.html gets rendered just in client. and i was so careful about writing tags in ssr exactlly like they are in index.html
solution but not the solution:
this problem is occuring because i use loadable component in my Router.js. so when i import components the normal way there is no blink and everything is fine but unused js decreases my page's perfomance score. i need loadable component stop making the page blink.
diving into the code:
just the client
index.html : rendered just in client
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta name="robots" content="noindex, nofollow" />
<meta data-rh="true" name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" />
<link href="%PUBLIC_URL%/fonts.css" rel="stylesheet" />
</head>
<body>
<div id="root"></div>
<script src="%PUBLIC_URL%/main.js"></script>
</body>
</html>
index.js : rendered just in client
import React from 'react'
import ReactDOM from 'react-dom'
import {loadableReady} from '#loadable/component'
import App from './App'
import {BrowserRouter} from 'react-router-dom'
import {HelmetProvider} from 'react-helmet-async'
import { Provider } from 'react-redux'
loadableReady(() => {
const preloadedState = window.__PRELOADED_STATE__
delete window.__PRELOADED_STATE__
ReactDOM.hydrate(
<BrowserRouter>
<HelmetProvider>
<Provider store={store(preloadedState)}>
<App />
</Provider>{" "}
</HelmetProvider>
</BrowserRouter>,
document.getElementById("root")
);
})
just the server
ssrIndex.js
require('ignore-styles')
require('#babel/register')({
ignore: [/(node_modules)/],
presets: ['#babel/preset-env', '#babel/preset-react'],
plugins: [
'#babel/plugin-transform-runtime',
'#babel/plugin-proposal-class-properties',
'babel-plugin-dynamic-import-node',
'#babel/plugin-transform-modules-commonjs',
'#loadable/babel-plugin',
],
})
// Import the rest of our application.
require('./SSR.js')
SSR.js
import React from 'react'
import express from 'express'
import ReactDOMServer from 'react-dom/server'
import {StaticRouter} from 'react-router-dom'
import {Provider} from 'react-redux'
import ServerStyleSheets from '#material-ui/styles/ServerStyleSheets'
import {HelmetProvider} from 'react-helmet-async'
import {ChunkExtractor, ChunkExtractorManager} from '#loadable/server'
import path from 'path'
import App from './App'
import store from './redux/store'
import template from './utils/template'
const PORT = 8080
const app = express()
const renderPage = (req, res, preload) => {
const staticRouterContext = {}
const helmetContext = {}
const statsFile = path.resolve(__dirname, '../build', 'loadable-component.json')
const extractor = new ChunkExtractor({statsFile})
const sheets = new ServerStyleSheets()
const html = ReactDOMServer.renderToString(
sheets.collect(
<ChunkExtractorManager extractor={extractor}>
<HelmetProvider context={helmetContext}>
<StaticRouter location={req.url} context={staticRouterContext}>
<Provider store={store(preload)}>
<App />
</Provider>
</StaticRouter>
</HelmetProvider>
</ChunkExtractorManager>,
),
)
const {helmet} = helmetContext
const wholeData = template('scripts', {
chunks: html,
helmet,
extractor,
sheets,
preload,
})
res.send(wholeData)
}
const serverRenderer = (req, res, next) => {
fetchSomeExternalData()
.then(response => {
// response.data is used as preloaded data and passed to the store of redux
// also stored in a variable called __PRELOADED_STATE__ in window to use in client side
// to populate store of redux
renderPage(req, response, response.data)
})
.catch(err => {
// start server side rendering without preloaded data
renderPage(req, res)
})
}
// each url that i want to render on the server side i should add here individually
// which is not so convenient
app.get('/', serverRenderer)
app.get('/my-url-1/', serverRenderer)
app.get('/my-url-2/', serverRenderer)
app.use(express.static(path.join(__dirname, '/../build/')))
// the * doesnt seem to work
app.get('*', serverRenderer)
app.listen(PORT, () => {
if (process.send) {
process.send('ready')
}
})
for both client and server
App.js
<div>
<Header/>
<Router/>
<Footer/>
</div>
i'd be happy to hear any suggestions or solutions. thank you for your time.
The App was initially made with CRA and converted into SSR. The app works fine but the problem arises only when I refresh the app or manually type in the URL to a certain page. The middle div's classname is then overridden to the classname of the middle div of the base route.
It changes for example from this:
<div id="root">
<div class="header">..</div>
<div class="newlist">..</div>
<div class="footer">..</div>
</div>
to this:
<div id="root">
<div class="header">..</div>
<div class="main-flex-container">..</div>
<div class="footer">..</div>
</div>
For extra information here is my src/index.js:
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import configureStore, { history } from './redux/store/index';
import AppRouter from './routers/AppRouter';
import './styles/styles.scss';
const store = configureStore();
require('dotenv').config();
const isServer = typeof window !== 'undefined';
if (isServer) {
ReactDOM.hydrate(
<Provider store={store}>
<BrowserRouter>
<AppRouter />
</BrowserRouter>
</Provider>,
document.getElementById('root'),
);
}
and here is my server.js:
import express from 'express';
import fs from 'fs';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { Provider } from 'react-redux';
import configureStore from '../src/redux/store/index';
import AppRouter from '../src/routers/AppRouter';
import { StaticRouter } from 'react-router-dom';
const app = express();
const store = configureStore();
app.use(express.static(path.resolve(__dirname, '..', 'build')));
app.use('/*', (req, res, next) => {
fs.readFile(path.resolve('./build/index.html'), 'utf-8', (err, data) => {
if (err) {
console.log(err);
return res.send(500).send('Error happened!');
}
return res.send(
data.replace(
'<div id="root"></div>',
`<div id="root">${ReactDOMServer.renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={{}}>
<AppRouter />
</StaticRouter>
</Provider>,
)}</div>`,
),
);
});
});
app.listen(3000, () => {
console.log('Listening...');
});
In case this isn't enough information, here is the link to the project on GitHub.
So the crux of your problem here seems to be that when rendered on the server, the router isn't rendering the correct component based on the url as we would expect.
What is happening is not that the class name is changed, instead we are actually always rendering the root page and then react replaces the content with the "new task" page on the client before you get a chance to have a look at it in the inspector.
Summary: When using app.use with a path in express, we are mounting an express middleware at the specified path, and express removes the "mount point" from the req.url provided to the middleware. Among other solutions, we can use req.originalUrl to get the actual url. req.originalUrl is a property provided by express that gives the url as it was before any middleware changed it.
Let's investigate!
If we extract the code that renders your app to a separate variable and log that, we will see that the logged HTML string is the page with the "New List" button on it.
const renderedApp = ReactDOMServer.renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={{}}>
<AppRouter />
</StaticRouter>
</Provider>,
);
console.log('Rendered app', renderedApp);
return res.send(
data.replace(
'<div id="root"></div>',
`<div id="root">${renderedApp}</div>`,
),
);
Visiting localhost:3000/l/new and looking in the log:
Rendered app <div class="header"><a class="header__button" id="header__colabico" href="/">COLABI.CO</a><div class="header__right"><button class="header__button" id="header__tweet">TWEET</button><button class="header__button" id="header__logout">LOGOUT</button></div></div><div class="main-flex-container"><a class="home__button" href="/l/new"> <!-- -->NEW LIST<!-- --> </a><p class="home__infotext"> <!-- -->Start by pressing that big button up there!<!-- --> </p></div><div class="footer"><div class="footer__left"><a class="footer__button" href="/privacy">PRIVACY</a><a class="footer__button" href="/terms">TERMS</a></div><p class="footer__colabico"> © colabi.co</p></div>
This looks very much like the root page instead of the new list page.
Aside: Putting the rendering on a separate line instead of inline in the template string also helps with syntax highlighting, as a nice bonus.
Aside 2: By adding this logging you'll also see that your code is not run when hitting the root url. The reason for this is probably because of the static middleware at the start, which will match on the index.html page in your build folder.
Now that we are fairly certain what the problem is, let's try to see why it might be.
The StaticRouter accepts the location as a prop, and we are passing in req.url for that. Let's verify that this is what we think it is.
console.log('URL:', req.url);
When visiting localhost:3000/l/new, this is logged:
URL: /
That is certainly not what we expected!
I went to look up the documentation for req.url to see what the problem might be, and found this method:
req.originalUrl
I couldn't see the documentation for req.url, but there's a helpful box here explaining why that is (req.url comes from Node's http module).
Logging req.originalUrl too just to see what that is for us, and sure enough:
original URL: /l/new
URL: /
req.originalUrl has what we need!
At this point I searched around for references to explain why req.url might be / in our case, and it turns out it's because of this:
When we are using app.use, we are mounting an express middleware at the specified path, rather than setting up a route handler (that would be app.get or similar), and middlewares remove the path they are mounted at from their own url, because they consider that their root. When we specify the middleware to be mounted at /*, the wildcard matches all routes and the whole url is removed.
In other words, we can fix this in a few different ways:
Keep it as it is, but use req.originalUrl instead
Remove the path from app.use (app.use((req, res, next) => {/* your code */}))
Change from app.use to app.get
Hope that helps!
I am trying to do a simple test and I know I am missing something simple.
I have two applications, one hosted on node (localhost:3000) and another is hosted on another site (http://development.azure). The section site is not running under node, its standard IIS.
In my node server application i have the following
import { renderToString } from "react-dom/server"
app.get("/", (req, res) => {
const name = 'Marvelous Peter'
res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>");
res.write("<div id='content'>");
res.write(renderToString(<Hello name={name} />));
res.write("</div></body></html>");
res.end();
});
app.listen(3000)
In my IIS application I am attempting to load the react component using xhr and then hydrate it.
const xhr = require("xhr");
xhr({
method: "GET",
uri: "http://localhost:3000",
}, (err, resp, body) => {
// check resp.statusCode
ReactDOM.hydrate(body, document.getElementById("test"), () => {
});
});
I am seeing the output on my page, however, it is not HTML encoded. I renders as text
<!DOCTYPE html><html><head><title>My Page</title></head><body><div id='content'><div data-reactroot=""><h1>Hello, <!-- -->Marvelous Peter<!-- -->!</h1></div></div></body></html>
I tried returning only the renderToString value and not HTML and still get the same thing, specifically since i have tags in the component
import React from 'react'
const Hello = (props) => (
<div><h1>Hello, {props.name}!</h1></div>
)
export default Hello
I know I am missing something, but I am finding it hard to find info on doing this and would appreciate any help!
Thanks in advance!
EDIT:
Per the suggestion below, I tried to create a simple component and then hydrate it but I still get the same response.
Here is the new component:
import * as React from "react";
import * as ReactDOM from "react-dom";
class Hello extends React.Component {
public load() {
const xhr = require("xhr");
let res = null;
xhr({
method: "get",
sync: true,
uri: "http://localhost:3000"
}, (err, resp, body) => {
res = body;
});
return res;
}
public render(): JSX.Element {
const val = this.load()
return val;
}
}
const target: any = document.querySelector("#main");
if (target) {
ReactDOM.hydrate(<Hello />, target);
}
I am rather stumped right now. Could it have to do with the response header type from the xhr request?
Any suggestions?
JSX !== HTML they may appear the same but they are fundamentally different and cannot be used interchangeably.
<div/> in JSX is React.createElement("div");
<div></div> in HTML is document.createElement("div");
You cannot use HTML (or a HTML string) in place of a function in React that expects JSX (mainly ReactDOM.hydrate, ReactDOM.render, React.createElement("div", null, NEED JSX HERE).
In order to render it not as a string you'll have to use the only API implementation that React provides to turn a HTML string into something resembling JSX which is...
dangerouslySetInnerHTML={{__html: YOUR STRING HERE}}
try this..
ReactDOM.render(<div dangerouslySetInnerHTML={{__html: YOURSTRINGHERE}}/>, document.body)
It's expects an Element, not a string.
ReactDOM.hydrate(
<MyComponent />,
document.getElementById('root')
)
Take that HTML response and create a react component out of it. Then pass it in your .hydrate method.
Is there any simple way to convert markdown text from contentful api to render into html code to be display on html page. I have tried using pagedown and some similar techniques , but none seem to work for me .
I'm a customer success manager at Contentful -
You can check out a list of recommended parsers by language on the our FAQ.
Also, feel free to send us messages on Intercom via our UI by clicking the 'Talk to Us' link :)
I know I'm late but here's the solution using handlebars:
var marked = require('marked');
marked.setOptions({
renderer: new marked.Renderer(),
sanitize: true,
smartLists: true,
smartypants: true
});
//Home
router.get('/', (req, res) => {
client.getEntry('<ENTRY_ID>')
.then( (entry)=> {
entry.fields.body = marked(entry.fields.body);
res.render('static/index',
{
entry: entry,
user: req.user
});
}).catch( (err) => {
console.log(err);
})
});
Then in our index.hbs template we can call the markdown variable in this case (entry.fields.body) by using {{{}}} to prevent escaping.
{{{entry.fields.body}}}
Here's how I did it with React:
class NewsDetail extends React.Component {
render() {
const body = marked(this.props.body || "");
return (
<div className="news-detail">
<h2>{this.props.title}</h2>
<div dangerouslySetInnerHTML={ { __html: body } }></div>
</div>
);
}
}
The markdown content is stored in the body attribute of the NewsDetail tag (via a short function that maps contentful data structure to my app structure).
The HTML page has this script tag to pull in the marked function:
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.6/marked.min.js"></script>
I have used ReactMarkdown module: in case you also have a react app: https://github.com/rexxars/react-markdown
Example: npm install --save react-markdown
const React = require('react')
const ReactDOM = require('react-dom')
const ReactMarkdown = require('react-markdown')
const input = '# This is a header\n\nAnd this is a paragraph'
ReactDOM.render(
<ReactMarkdown source={input} />,
document.getElementById('container')
)
I am passing markdown through props and using this module inside of my child component.
I also did the same as margarita in a react app but in the child component and I pulled my markdown from contentful.
I installed react-markdown package
with
npm install react-markdown
import React from "react";
import ReactMarkdown from "react-markdown";
const AboutIntro = (props) => {
return (
<div>
<h2 className="about__intro-title">
{props.aboutTitle}
</h2>
<ReactMarkdown>
{props.aboutCopy}
</ReactMarkdown>
</div>
)
}
export default AboutIntro;
hope this helps