question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Server side rendering still showing `Loading...`

See original GitHub issue

When a component is rendered server side, it always renders as ‘Loading…’. As a result, no components rendered via react-universal-component is synchronously rendered.

I’ve added the LimitChunkCountPlugin, and set the externals according to the README. Can you highlight what I may have done incorrectly?

webpack.prod.server.babel.js

export default {
  devtool: 'source-map',
  entry: './src/server/server.js',
  target: 'node', // ignore built-in modules like path, fs, etc.
  node: {
    __dirname: false,
    __filename: false,
  },
  externals: [
    nodeExternals({
      whitelist: ['react-universal-component', 'webpack-flush-chunks']
    }),
  ], // ignore all modules in node_modules folder
  output: {
    path: path.resolve(root, 'dist'),
    filename: 'server.js',
    chunkFilename: '[name]-[chunkhash:6].js',
    pathinfo: true,
    publicPath: '/dist/',
  },
  resolve: {
    modules: [
      'assets',
      'src',
      'node_modules',
    ],
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
          babelrc: false,
          presets: [
            [
              'es2015',
              {
                'modules': false
              }
            ],
            'latest',
            'react',
            'stage-0',
          ],
          plugins: [
            [
              'universal-import',
              {
                'babelServer': true
              }
            ],
            [
              "css-modules-transform",
              {
                "generateScopedName": "[hash:base64:6]"
              }
            ],
            'transform-decorators-legacy',
            'transform-flow-strip-types',
            'array-includes',
            'transform-async-to-generator',
            path.resolve(root, 'buildTools/graphql/babelRelayPlugin'),
          ],
        },
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: 'css-loader/locals',
            options: {
              importLoaders: 1,
              modules: true,
              localIdentName: '[hash:base64:6]',
            }
          },
          'postcss-loader',
        ],
      },
      {
        test: /\.sass$/,
        use: [
          {
            loader: 'cache-loader',
          },
          {
            loader: 'css-loader/locals', // css/locals doesn't embed css but only exports the identifier mappings
            options: {
              modules: true,
              importLoaders: 2,
              localIdentName: '[hash:base64:6]',
            }
          },
          'postcss-loader',
          {
            loader: 'sass-loader',
            options: {
              indentedSyntax: true,
            }
          }
        ],
      },
      {
        test: /\.(ico|jpg|png|gif|eot|ttf|woff|woff2)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 5000,
          name: 'static/[name]-[hash:6].[ext]',
        },
        exclude: faviconRegex,
      },
      // force favicon files to be saved as files
      {
        test: faviconRegex,
        loader: 'url-loader',
        options: {
          limit: 1,
          name: 'static/[name].[ext]',
        },
      },
      // icons loader
      {
        test: /^(?!.*keepfill\.icon\.svg$).*icon\.svg$/,
        use: [
          {
            loader: 'babel-loader',
          },
          {
            loader: 'svg-react-loader',
          },
          {
            loader: 'svgo-loader',
            options: {
              plugins: [
                {removeTitle: true},
                {removeAttrs: {attrs: 'fill'}},
              ],
            },
          },
        ],
      },
      {
        test: /keepfill\.icon\.svg$/,
        use: [
          {
            loader: 'babel-loader',
          },
          {
            loader: 'svg-react-loader',
          },
          {
            loader: 'svgo-loader',
            options: {
              plugins: [
                {removeTitle: true},
              ],
            },
          },
        ],
      },
      // svg loader excluding the .icon.svg files
      {
        test: /^(?!.*icon\.svg$).*\.svg$/,
        loader: 'url-loader',
        options: {
          limit: 5000,
        },
      },
      {
        test: /\.html$/,
        loader: 'html-loader',
      },
    ],
  },
  plugins: [
    new webpack.LoaderOptionsPlugin({
      debug: true,
      options: {
        postcss: [
          easyImport(),
          cssnext(),
        ],
        context: appSourceFolder,
        output: { path :  './' },
      },
    }),
    new webpack.BannerPlugin({
      banner: 'require("source-map-support").install();',
      raw: true,
      entryOnly: false
    }),
    new webpack.ProvidePlugin({
      React: 'react', // provides React globally so that we don't need to import it
      cssModules: 'react-css-modules',
      classnames: 'classnames', // still used in old code
      classnamesBind: 'classnames/bind',
      Logger: 'universal/utils/log',
    }),
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production'),
      },
      __IS_BROWSER__: false,
    }),
    new webpack.optimize.LimitChunkCountPlugin({
      maxChunks: 1,
    })
  ],
};

webpack.prod.client.babel.js

import cssnext from 'postcss-cssnext';
import easyImport from 'postcss-easy-import';
import path from 'path';
import webpack from 'webpack';
//import ExtractTextPlugin from 'extract-text-webpack-plugin';
import OptimizeCssAssetsPlugin from 'optimize-css-assets-webpack-plugin';
import AssetsPlugin from 'assets-webpack-plugin';
import ExtractCssChunks from 'extract-css-chunks-webpack-plugin';
import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer';
import {nodeModules2, nodeModules3} from './chunks';

const root = path.resolve(__dirname, '..', '..');
const appSourceFolder = path.resolve(root, 'src');
const faviconRegex = /favicon.*\.(png|ico)/;

// Create multiple instances
// we are migrating slowly away from sass to postcss and that all new code should be in postcss
const extractCss = new ExtractCssChunks({
  filename: '[name]-post-[chunkhash:6].css',
  ignoreOrder: true,
});

const extractSass = new ExtractCssChunks({
  filename: '[name]-[chunkhash:6].css',
  ignoreOrder: true,
});

export default {
  devtool: 'source-map',
  entry: ['babel-polyfill', './src/client/index'],
  target: 'web',
  output: {
    path: path.resolve(root, 'dist'),
    filename: '[name]-[chunkhash:6].js',
    chunkFilename: '[name]-[chunkhash:6].js',
    publicPath: '/dist/',
  },
  externals: [{
    config: 'config',
  }],
  resolve: {
    modules: [
      'assets',
      'src',
      'node_modules',
    ],
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        include: appSourceFolder,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },
      {
        test: /\.css$/,
        use: extractCss.extract([
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              modules: true,
              localIdentName: '[hash:base64:6]',
            }
          },
          'postcss-loader',
        ]),
      },
      {
        test: /\.sass$/,
        use: extractSass.extract({
          use: [
            {
              loader: 'cache-loader',
            },
            {
              loader: 'css-loader',
              options: {
                modules: true,
                importLoaders: 3,
                localIdentName: '[hash:base64:6]',
              }
            },
            'resolve-url-loader',
            'postcss-loader',
            'sass-loader',
          ],
        }),
      },
      {
        test: /\.(ico|jpg|png|gif|eot|ttf|woff|woff2)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 5000,
          name: 'static/[name]-[hash:6].[ext]',
        },
        exclude: faviconRegex,
      },
      // force favicon files to be saved as files
      {
        test: faviconRegex,
        loader: 'url-loader',
        options: {
          limit: 1,
          name: 'static/[name].[ext]',
        },
      },
      // icons loader
      {
        test: /^(?!.*keepfill\.icon\.svg$).*icon\.svg$/,
        use: [
          {
            loader: 'babel-loader',
          },
          {
            loader: 'svg-react-loader',
          },
          {
            loader: 'svgo-loader',
            options: {
              plugins: [
                {removeTitle: true},
                {removeAttrs: {attrs: 'fill'}},
              ],
            },
          },
        ],
      },
      {
        test: /keepfill\.icon\.svg$/,
        use: [
          {
            loader: 'babel-loader',
          },
          {
            loader: 'svg-react-loader',
          },
          {
            loader: 'svgo-loader',
            options: {
              plugins: [
                {removeTitle: true},
              ],
            },
          },
        ],
      },
      // svg loader excluding the .icon.svg files
      {
        test: /^(?!.*icon\.svg$).*\.svg$/,
        loader: 'url-loader',
        options: {
          limit: 5000,
        },
      },
      {
        test: /\.html$/,
        loader: 'html-loader',
      },
    ],
  },
  plugins: [
    new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
    new webpack.NormalModuleReplacementPlugin(
      /moment-timezone\/data\/packed\/latest\.json/,
      path.resolve(root, 'buildTools', 'moment', 'timezoneDefinitions.json')
    ),
    new webpack.LoaderOptionsPlugin({
      debug: true,
      options: {
        postcss: [
          easyImport(),
          cssnext(),
        ],
        context: appSourceFolder,
        output: {path: './'},
      },
    }),
    // prevents Webpack from creating chunks that would be too small to be worth loading separately
    new webpack.optimize.MinChunkSizePlugin({
      minChunkSize: 51200, // ~50kb
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false,
        screw_ie8: true,
        conditionals: true,
        unused: true,
        comparisons: true,
        sequences: true,
        dead_code: true,
        evaluate: true,
        join_vars: true,
        if_return: true,
      },
    }),
    new webpack.NoEmitOnErrorsPlugin(),
    new webpack.ProvidePlugin({
      React: 'react', // provides React globally so that we don't need to import it
      cssModules: 'react-css-modules',
      classnames: 'classnames', // still used in old code
      classnamesBind: 'classnames/bind',
      Logger: 'universal/utils/log',
    }),
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production'),
      },
      __IS_BROWSER__: true,
    }),
    extractCss,
    extractSass,
    new OptimizeCssAssetsPlugin(),
    // outputs a json file with the paths of the generated assets
    new AssetsPlugin({
      filename: 'webpack-assets.json',
      path: path.resolve(root, 'dist'),
    }),
    new webpack.optimize.ModuleConcatenationPlugin(),
    // Un-comment below to see a visual report of the size of each bundle
    // new BundleAnalyzerPlugin({
    //   analyzerMode: 'static',
    // }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'nodeModules1',
      filename: 'nodeModules1-[chunkhash:6].js',
      minChunks: (module) => {
        const context = module.context; // directory name
        return context && context.indexOf('node_modules') >= 0;
      },
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'nodeModules2',
      chunks: ['nodeModules1'],
      filename: 'nodeModules2-[chunkhash:6].js',
      minChunks: (module) => {
        const context = module.context;
        return context && nodeModules2.find(t => context.indexOf(`node_modules/${t}`) >= 0);
      },
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'nodeModules3',
      chunks: ['nodeModules1'],
      filename: 'nodeModules3-[chunkhash:6].js',
      minChunks: (module) => {
        const context = module.context;
        return context && nodeModules3.find(t => context.indexOf(`node_modules/${t}`) >= 0);
      },
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity,
    }),
  ],
};

.babelrc

{
  "presets": [
    "flow",
    [
      "es2015",
      {
        "modules": false
      }
    ],
    "latest",
    "react",
    "stage-0"
  ],
  "plugins": [
    "universal-import",
    "transform-decorators-legacy",
    "transform-flow-strip-types",
    "array-includes",
    "transform-async-to-generator",
    "./buildTools/graphql/babelRelayPlugin"
  ]
}

Another question: on the server side, is the flow supposed to be like: requireSync -> resolve -> mod -> resolveExport such that Component is loaded synchronously?

Thanks in advance.

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:1
  • Comments:25 (11 by maintainers)

github_iconTop GitHub Comments

5reactions
bknifflercommented, Oct 28, 2017

Or

externals = nodeExternals({
      whitelist: [
        '.bin',
        'source-map-support/register',
        // 'babel-polyfill',
        /\.(eot|woff|woff2|ttf|otf)$/,
        /\.(svg|png|jpg|jpeg|gif|ico)$/,
        /\.(mp4|mp3|ogg|swf|webp)$/,
        /\.(css|scss|sass|sss|less)$/,
        v =>
          v.indexOf('babel-plugin-universal-import') === 0 ||
          v.indexOf('react-universal-component') === 0,
      ],
    });
3reactions
jkettmanncommented, Nov 26, 2017

In case it helps anybody: I had this problem too. I tried to find out more following the debugging steps in this comment. Inside node_modules/react-universal-component/dist/requireUniversalModule.js requireSync was called and a weakId was returned. But __webpack_modules__[weakId] was undefined.

What helped in the end was removing the babelServer: true from my babel config.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Next.js getServerSideProps loading state - Stack Overflow
js component to show a Loading component while the getServerSideProps is doing async work like a fetch as shown here https://stackoverflow.com/a ...
Read more >
How Server Side Rendering improves initial loading times
More to know about Server Side Rendering​​ This is to make sure all data on your site can be indexed by search engines,...
Read more >
What is server-side rendering and how does it improve site ...
Server-side rendering ensures that website content appears quickly, without first having to download and run application code.
Read more >
Using React Suspense for Better Server-Side Rendering
Server -side rendering is a technique where React components are compiled into HTML server-side and sent to clients. So, end-users will not have...
Read more >
Server-Side Vs. Client-Side Rendering — All You Need to Know
The webpage needs to be rendered before use, which is one reason the first page doesn't load quite as fast. On the other...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found