I have a react server-side rendering application along with node & express js.
Routes.js
import React from 'react';
import { Route } from 'react-router-dom';
import Home from './Home';
import Test from './Test';
export default () => {
return (
<div>
<Route exact path="/" component={Home} />
<Route exact path="/test/:deviceId" component={Test} />
</div>
);
};
index.js
import express from 'express';
import renderer from './helpers/renderer';
const app = express();
app.use(express.static('public')));
app.get('*', (req, res) => {
console.log(req.url);
res.send(renderer(req));
});
app.listen(3090, () => {
console.log('listening at http://localhost:3090');
})
renderer.js
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import Routes from '../components/Routes';
export default req => {
const content = renderToString(
<StaticRouter location={req.path} context={{}}>
<Routes />
</StaticRouter>
);
return `
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div id="app">${content}</div>
<script src="bundle.js"></script>
</body>
</html>
`;
};
client.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import Routes from './Routes';
ReactDOM.hydrate(
<BrowserRouter>
<Routes />
</BrowserRouter>
, document.getElementById('app'));
Well, I am trying to pass a deviceId along with my route /test/:deviceId,
and my HTML template under renderer.js looks for bundle.js file under /test/build.js instead of /bundle.js
How do I make sure my bundle file always points to the correct location which is under public folder exposed by
app.use(express.static('public'));
if I visit, / route to Home component, it looks for bundle.js under correct folder which is the public folder.
Do let me know if you need any extra information.
Well, finally I have figured out what was changing the path of bundle.js with each out.
if my route is '/', it will look for bundle.js at http://localhost:3090/bundle.js
if the route is '/test/:deviceId', it will look for bundle.js at http://localhost:3090/test/:deviceId/bundle.js (in this scenario it will never find bundle.js)
I fixed it my modifying the html temlate under renderer.js
for the tag I changed path to bundle.js from 'bundle.js' to '/bundle.js'
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import Routes from '../components/Routes';
export default req => {
const content = renderToString(
<StaticRouter location={req.path} context={{}}>
<Routes />
</StaticRouter>
);
return `
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div id="app">${content}</div>
<script src="/bundle.js"></script>
</body>
</html>
`;
};
Now, it will always look for bundle.js under http://localhost:3090/bundle.js
Related
I have a super simple react app like so.
index.tsx
App.tsx
Main.tsx
Home.tsx
index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { BrowserRouter as Router } from "react-router-dom";
import { ApolloProvider } from "react-apollo";
import client from "./utils/client";
ReactDOM.render(
<Router>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</Router>,
document.getElementById('root')
);
App.tsx
import React from 'react';
import Main from "./Main";
function App() {
return (
<Main />
);
}
export default App;
Main.tsx
import React from 'react';
import { Switch, Route, Link } from "react-router-dom";
import Home from "./Home";
const Main:React.FC = () => {
return (
<div>
<Switch>
<Route exact path='/' component={Home} />
</Switch>
</div>
);
};
export default Main;
Home.tsx
import React, {useContext} from 'react';
import { Link } from "react-router-dom";
const Home:React.FC = () => {
return (
<div>
<h1>Home</h1>
<Link to='http://google.com'> Google</Link>
</div>
);
};
export default Home;
I have an App.test.tsx with a dummy test just to run it.
import React from 'react';
import { render, screen } from '#testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
expect(true).toBeTruthy
});
If I run the test with yarn test
I get an error:
Invariant failed: You should not use <Link> outside a <Router>
The Link I have is in Home.tsc which is surrounded by <Router> in index.tsx
I'm I doing something work here.
The app runs without any errors.
This error only appears when I run the test
There are two solutions below, the first is to add <Router> component into your test case. The second option is to switch from <Link> to a simple anchor tag.
Option 1:
You can add <Router> component into your test also, so it won't missing there as:
test('renders learn react link', () => {
render(<Router>
<App />
</Router>);
expect(true).toBeTruthy
});
Option 2:
Also you can change from <Link> component to a simple anchor tag because it creates the same end result based on your code from:
<Link to='http://google.com'> Google</Link>
To the following in <Home> component:
Google
Then at the end you can keep your original test case.
I am new in server side rendering. I am trying to implement it like following but, it shows error.
index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
index.js
import React from 'react';
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk'
import { Provider } from 'react-redux'
import rootReducer from './reducers'
import './index.css'
import Main from './Main'
const preloadedState = window.__PRELOADED_STATE__
delete window.__PRELOADED_STATE__
const store = createStore(
rootReducer,
preloadedState,
applyMiddleware(thunk)
);
ReactDOM.hydrate(
<Provider store={store}>
<Main />
</Provider>,
document.getElementById('root')
);
server.js
import express from 'express';
import open from 'open';
import rootReducer from '../src/reducers'
import Main from '../src/Main';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { renderToString } from 'react-dom/server'
var app = express();
const port = 4000;
app.use(express.static('dist'));
app.get('/', (req, res) => {
const store = createStore(rootReducer)
const html = renderToString(
<Provider store={store}>
<Main />
</Provider>
)
const preloadedState = store.getState();
res.send(renderFullPage(html, preloadedState))
});
function renderFullPage(html, preloadedState) {
return `
<!doctype html>
<html>
<body>
<div id="root">${html}</div>
<script>
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g,'\\u003c')}
</script>
</body>
</html>
`
}
app.listen(port, function (err) {
if (err) {
console.log(err);
} else {
open('http://localhost:' + port);
}
})
When i try to run SSR it shows following error. I don't understand what i am doing wrong?
Warning: Expected server HTML to contain a matching <div> in <div>.
Any help would be greatly appreciated.
Edit:
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Content from './components/Content';
import PageNotFound from './components/PageNotFound'
import Movie from './components/Movie';
class Main extends Component {
render() {
return (
<div className="main">
<Router>
<Switch>
<Route exact path='/' component={Content} />
<Route path='/search/:query' component={Content} />
<Route path='/film/:id' component={Movie} />
<Route path='*' component={PageNotFound} />
</Switch>
</Router>
</div>
);
}
}
export default Main;
This is not duplicate of given link as i am using SSR. Then why should i use render instead of hydrate as i am using SSR?
I'm very new to React and ran into an issue when trying to import a "sub-component", for lack of a better word.
In my App.js file I imported my header class: import Header from './Components/Header/Header'; Which worked fine.
Within my Header.js file I'm using router to select different components. However, when I try to import my Home class: import Home from '../Subcomponents/HomePage/HomePage'; I receive the following error: Module not found: Can't resolve '../Subcomponents/HomePage/HomePage'
My file structure is:
app.js
Components/Header/Header.js
Subcomponents/HomePage/HomePage.js
App.js Code:
import React, { Component } from 'react';
import Header from './Components/Header/Header';
import Footer from './Components/Footer/Footer';
import Body from './Components/Body/Body';
import './Assets/css/mainCSS.css';
class App extends Component {
render() {
return (
<div className="App">
<Header />
<Body />
<Footer/>
</div>
);
}
}
export default App;
Header Code:
import React from 'react';
import Home from '../Subcomponents/HomePage/HomePage';
import { Router, Route, Link } from 'react-router-dom';
const header = () => {
return <header>
<Router>
<nav>
<ul>
<li>
<Link to='/'>Home</Link>
</li>
</ul>
<hr />
<Route excat path ="/" component={Home} />
</nav>
</Router>
</header>
}
export default header;
HomePage Code:
import React from 'react';
const homepage =() =>{
return <p>
homepage working
</p>
}
export default homepage;
Am I doing something wrong here or is this not possible in React? Any advice would be greatly appreciated!
Thanks!
From Header.js, ../ puts you into Components, not into the parent. It should be '../../Subcomponents/HomePage/HomePage'.
Also, imho: within each component folder, name the file index.js so that it will be automatically exported. Than you can just do: '../../Subcomponents/HomePage'
I started adding routes to my app, the problem is that even after I render them I cannot access them (get a 404). I tried this, this and this, but I cannot make it work. This is my index
import React from 'react'
import ReactDOM from 'react-dom';
import App from './containers/App';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
const app = (
<BrowserRouter>
<App />
</BrowserRouter>
);
ReactDOM.render(app, document.getElementById('root'));
and my App.jsx is just
import React, { Component } from 'react';
import {Route, Switch} from 'react-router-dom';
class App extends Component {
render() {
return (
<div>
<Switch>
<Route exact path = '/foo' render = {() => <h1>foo</h1>} />
<Route exact path = '/' render = {() => <h1>Home</h1>} />
</Switch>
<a href = '/foo'>Go to foo</a>
</div>
)
}
}
export default App;
In theory, if I click on the anchor tag, I should redirected to the foo path, but instead I get a 404. Any suggestion is greatly appreciated
You can use the Link component of react-router-dom:
import { Link } from 'react-router-dom'
<Link to="/foo">Go to foo</Link>
This will generate an <a> tag but will respect the client routing
Here is the solution!
Remove exact from exact path = '/foo'
With a Tag, page will get loaded, hence better to use <Link to='/foo'>roster</Link>
Here is the example which you can refer
You can import {Link} from react-router it gives an a tag and you can redirect
I setup simple route within my index.js file.
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import reduxThunk from 'redux-thunk';
import '../less/app.less';
import reducers from './reducers';
import App from './components/App';
import Login from './components/auth/Login';
import Welcome from './components/Welcome';
// const defaultSetting = settings;
const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={Welcome} />
<Route path="login" component={Login} />
</Route>
</Router>
</Provider>
, document.querySelector('.container')
);
my App.js
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div>
{this.props.children}
</div>
);
}
}
export default App;
When I navigate to localhost:8080 using webpack-dev-server I can properly show my index route. but when I navigate to localhost:8080/login' it shows errorCannot GET /login`.
Any solution?
By default the server will look for an html file at the /login route. So you should configure it for html5 navigation to return you index.html for any route it receives.
EDIT:
To do so in webpack, as you suggest in the comments, you can add this to your webpack dev server config:
historyApiFallback: true
index.html should be the default, so no need to specify it.
Also please note that urls containing dots are still pointing to files and thus redirected to the server. For example, if you have an url such as /search/firsname.lastname you would need to add a handler for this.
historyApiFallback: {
rewrites: [
{
from: /^\/search\/.*$/,
to: function() {
return 'index.html';
}
}
]
},
See this issue for more info.