7

I'm working on moving my react v0.14+ redux v3.0 + react-router v1.0 codebase from client-side rendering to server-side rendering using webpack v1.12 to bundle and code-split into chunks to load routes and components on-demand.

Im following and basing my setup on https://github.com/rackt/example-react-router-server-rendering-lazy-routes as I think it provides simplicity and great utilities. All day yesterday I have been working on moving to server-side rendering but I run into a few issues I haven't been able to solve and I'm not completely sure if they are because of webpack not being setup correctly, if am doing something wrong with react-router on the server/client or the routes config, or if its something I'm doing wrong with setting up redux that is causing these issues.

I run into the following issues:

  1. I'm able to load the initial page and everything works well but no other routes load and gives me GET http://localhost:3000/profile 404 (Not Found)
  2. The index/home page javascript works but all assets(css) are rendered as text/javascript so the styles don't show up unless they are inline.

webpack.config.js

var fs = require('fs')
var path = require('path')
var webpack = require('webpack')

module.exports = {

  devtool: 'source-map',

  entry: './client/client.jsx',

  output: {
    path: __dirname + '/__build__',
    filename: '[name].js',
    chunkFilename: '[id].chunk.js',
    publicPath: '/__build__/'
  },

  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /(node_modules|bower_components)/,
        loader: 'babel-loader'
      }
    ]
  },

  plugins: [
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.UglifyJsPlugin({
      compressor: { warnings: false },
    }),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
    })
  ]

}

server.js

import http from 'http';
import React from 'react';
import {renderToString} from 'react-dom/server';
import { match, RoutingContext } from 'react-router';
import {Provider} from 'react-redux';
import configureStore from './../common/store/store.js';

import fs from 'fs';
import { createPage, write, writeError, writeNotFound, redirect } from './server-utils.js';
import routes from './../common/routes/rootRoutes.js';

const PORT = process.env.PORT || 3000;

var store = configureStore();
const initialState = store.getState();

function renderApp(props, res) {
  var markup = renderToString(
    <Provider store={store}>
      <RoutingContext {...props}/>
    </Provider>
  );
  var html = createPage(markup, initialState);
  write(html, 'text/html', res);
}

http.createServer((req, res) => {

  if (req.url === '/favicon.ico') {
    write('haha', 'text/plain', res);
  }

  // serve JavaScript assets
  else if (/__build__/.test(req.url)) {
    fs.readFile(`.${req.url}`, (err, data) => {
      write(data, 'text/javascript', res);
    })
  }

  // handle all other urls with React Router
  else {
    match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
      if (error)
        writeError('ERROR!', res);
      else if (redirectLocation)
        redirect(redirectLocation, res);
      else if (renderProps)
        renderApp(renderProps, res);
      else
        writeNotFound(res);
    });
  }

}).listen(PORT)
console.log(`listening on port ${PORT}`)

server-utils

Is the same as from the repo that I posted above example-react-router-server-rendering-lazy-routes just navigate to /modules/utils/server-utils.js in the repo.The only difference is the createPage function:

export function createPage(html, initialState) {
  return( `
  <!doctype html>
  <html>
    <head>
      <meta charset="utf-8"/>
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link rel="stylesheet" href="./../bower_components/Ionicons/css/ionicons.min.css">
      <link rel="stylesheet" href="./../dist/main.css">
      <title>Sell Your Soles</title>
    </head>
    <body>
      <div id="app">${html}</div>
      <script>window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};</script>
      <script src="/__build__/main.js"></script>
    </body>
  </html>
  `);
}

rootRoute.js

// polyfill webpack require.ensure
if (typeof require.ensure !== 'function') require.ensure = (d, c) => c(require)

import App from '../components/App.jsx'
import Landing from '../components/Landing/Landing.jsx'

export default {
  path: '/',
  component: App,
  getChildRoutes(location, cb) {
    require.ensure([], (require) => {
      cb(null, [
        require('./UserProfile/UserProfileRoute.js'),
        require('./UserHome/UserHomeRoute.js'),
        require('./SneakerPage/SneakerPageRoute.js'),
        require('./Reviews/ReviewsRoute.js'),
        require('./Listings/ListingsRoute.js'),
        require('./Events/EventsRoute.js')
      ])
    })
  },
  indexRoute: {
    component: Landing
  }
}

userProfileRoute.js

import UserProfile from '../../components/UserProfile/UserProfile.jsx';

export default {
  path: 'profile',
  component: UserProfile
}

client.js

import React from 'react';
import { match, Router } from 'react-router';
import { render } from 'react-dom';
import { createHistory } from 'history';
import routes from './../common/routes/rootRoutes.js';
import {Provider} from 'react-redux';
import configureStore from './../common/store/store.js';


const { pathname, search, hash } = window.location;
const location = `${pathname}${search}${hash}`;

const initialState = window.__INITIAL_STATE__;
const store = configureStore(initialState);



// calling `match` is simply for side effects of
// loading route/component code for the initial location
match({ routes, location }, () => {
  render(
    <Provider store={store}>
      <Router routes={routes} history={createHistory()} />
    </Provider>,
    document.getElementById('app')
  );
});
kennet postigo
  • 525
  • 1
  • 5
  • 17

2 Answers2

11

I helped you out on discord, but I thought I'd post the answer here as well.

If you are using babel6 (instead of babel5) and using export default in your components, then you need to update your routes to the following:

getChildRoutes(location, cb) {
    require.ensure([], (require) => {
        cb(null, [
            require('./UserProfile/UserProfileRoute.js').default,
            require('./UserHome/UserHomeRoute.js').default,
            require('./SneakerPage/SneakerPageRoute.js').default,
            require('./Reviews/ReviewsRoute.js').default,
            require('./Listings/ListingsRoute.js').default,
            require('./Events/EventsRoute.js').default
        ])
    })
}

See this SO discussion for more details: Babel 6 changes how it exports default

Community
  • 1
  • 1
mjrussell
  • 615
  • 6
  • 7
  • 2
    You could also add the [add-module-exports](https://www.npmjs.com/package/babel-plugin-add-module-exports) babel-plugin if you don' t want to write `.default` on every require. – ofhouse May 28 '16 at 16:03
3

If you happen to upgrade to Webpack 2 (+ tree shaking), you would use System.import instead of requires, which is incredibly useful.

Here's how:

import App from 'containers/App';
function errorLoading(err) {
  console.error('Dynamic page loading failed', err);
}
function loadRoute(cb) {
  return (module) => cb(null, module.default);
}
export default {
  component: App,
  childRoutes: [
    {
      path: '/',
      getComponent(location, cb) {
        System.import('pages/Home')
          .then(loadRoute(cb))
          .catch(errorLoading);
      }
    },
    {
      path: 'blog',
      getComponent(location, cb) {
        System.import('pages/Blog')
          .then(loadRoute(cb))
          .catch(errorLoading);
      }
    }
  ]
};

You can get the entire guide in this blog post: Automatic Code Splitting for React Router

Grgur
  • 7,092
  • 2
  • 19
  • 34
  • Would you happen to know when webpack 2 will be released? Or is it still in beta? – kennet postigo Mar 10 '16 at 20:58
  • @kennetpostigo still in beta for now. The team is making enormous effort to finish the release, but I can't speak to the timelines – Grgur Mar 13 '16 at 14:50
  • Hello. And I I want to do server rendering? When I am trying to implement this with `System.import` I am getting `System.import is not a function` error – Stalso Jun 09 '16 at 12:45
  • Your server side doesn't know how to parse System.import. The easy way for now is to go with react.ensure on the server side. – Grgur Jun 09 '16 at 12:55
  • `System.import()` is now deprecated – Alex Shwarc Feb 08 '17 at 18:54