Reputation: 4132
I am developing a react app that uses server-side rendering. My routes work, except the homepage. Not sure why. I'm assuming I'm not setting up my express server correctly to handle the index route...?
The following is the module which handles express' setup and whatnot. I'm assuming I have the app.get('*)
or app.use(express.static)
incorrect.
app.js (server)
require('ignore-styles')
const compression = require('compression')
const express = require('express')
const path = require('path')
require('babel-register')({
ignore: /\/(build|node_modules)\//,
presets: ['env', 'react-app']
})
const universalLoader = require('./universal')
const app = express()
// Support Gzip
app.use(compression())
// Serve static assets
app.use(express.static(path.resolve(__dirname, '..', 'build')))
// Always return the main index.html, so react-router render the route in the client
app.get('*', universalLoader)
module.exports = app
universalLoader.js (server)
import path from 'path'
import fs from 'fs'
import React from 'react'
import { Provider } from 'react-redux'
import { renderToString } from 'react-dom/server'
import { StaticRouter, matchPath } from 'react-router-dom'
import configureStore from '../src/store'
import App from '../src/components/App'
import routes from '../src/shared/routes'
import { getSiteInfo } from '../src/store/actions/siteInfo'
import { REACT_APP_SITE_KEY } from '../src/shared/vars'
import Helmet from 'react-helmet'
module.exports = function universalLoader(req, res, next) {
// console.log('Loading....')
const store = configureStore()
const fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl
let routeFound = false
// Try to find a matched route
const promises = routes.reduce((promise, route) => {
var props = matchPath(req.url, route)
// If route was matched, component exists, and has an initialAction
// then call it's initialAction.
// This action will most-likely load some data asyncronously
if (props && route.component && route.component.initialAction) {
promise.push(Promise.resolve(store.dispatch(route.component.initialAction(store, props))))
}
return promise
}, [])
// Load initial site data
promises.push(Promise.resolve(store.dispatch(getSiteInfo(REACT_APP_SITE_KEY))))
// Wait until all async data has been loaded
Promise.all(promises)
.then(() => {
// Load index file path
const filePath = path.resolve(__dirname, '..', 'build', 'index.html')
// Read index file
fs.readFile(filePath, 'utf8', (err, htmlData) => {
if(err){
console.error('read err', err)
return res.status(404).end()
}
const preloadedState = store.getState()
// console.log("PreloadedState:", preloadedState)
const context = preloadedState
// console.log(context)
// Note: Pass in serverRequest prop so the App knows the domain it's on for meta tags
const markup = renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<App serverRequest={req} serverResponse={res} />
</StaticRouter>
</Provider>
)
const helmet = Helmet.renderStatic()
// Somewhere a `<Redirect>` was rendered
if(context.url){
console.log('Redirected:', context.url)
redirect(301, context.url)
// we're good, send the response
}else{
// Page meta data
const meta = helmet.title.toString() + helmet.meta.toString() + helmet.link.toString()
// Prep state to be injected into DOM for client
const pageState = `<script>window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}</script>`
// Inject state and markup
const RenderedApp = htmlData
.replace('<script></script>', pageState) // Store state to pass to client
.replace('<meta name="helmet">', meta) // Meta data
.replace('{{SSR}}', markup) // Actual markup/component html
console.log("SSR Rendered: ", req.path)
res.send(RenderedApp)
}
})
})
.catch(err => {
console.log("Error:", err)
})
}
I am console.log()
-ing when a route is being handled within universalLoader()
. All routes show in the console that stuff is happening. Except my homepage. It does not even show the "Loading..." message.
Upvotes: 1
Views: 316
Reputation: 29112
express.static
will be serving up any files in your build
directory. If it finds the requested file it will serve it up and end the request/response. No middleware registered after express.static
will get the chance to run if a suitable file is found.
Based on this line:
const filePath = path.resolve(__dirname, '..', 'build', 'index.html')
it would appear that you have a file called index.html
in your build
directory. This will get served up by express.static
when you hit the URL for index.html
but it will also get served up if you just hit the /
URL because express.static
defaults to serving up index.html
. See the index
property here:
https://expressjs.com/en/4x/api.html#express.static
The directory you point express.static
at needs to contain files that are static, i.e. that require no processing whatsoever. If any of the files need processing they need to live elsewhere. Note the parallels with how an Express app typically has a separate folder for views
, which in many ways is similar to what you're trying to do.
I would also suggest commenting out express.static
to see what effect that has. It should be a quick way to confirm that express.static
is responsible for stopping your index route being reached.
Update
Based on your comments it would seem that you do have a static directory at build/static
that contains your JS and CSS files. You can serve this up directly using:
app.use(express.static(path.resolve(__dirname, '..', 'build', 'static')))
However, this will cause all your URLs to change too, so http://localhost/static/js/example.js
will now be http://localhost/js/example.js
. To retain the original URLs you would need to put the static
back in via the route path:
app.use('/static', express.static(path.resolve(__dirname, '..', 'build', 'static')))
Upvotes: 3