Gurnzbot
Gurnzbot

Reputation: 4132

Express homepage not rendered on server

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

Answers (1)

skirtle
skirtle

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

Related Questions