AboutBlogContact
Frontend EngineeringMarch 14, 2015 4 min read 190Updated: June 22, 2026

How to Set Up React with Webpack 1.x Without Create React App (2015)

AunimedaAunimeda
📋 Table of Contents

Short answer: Install webpack@1, babel-loader@5, react@0.13, configure webpack.config.js with JSX transform and HMR. No CLI tools, no abstractions - just config files you control.

Create React App launched in July 2016. Before that, every React project started with manual Webpack configuration. This is what a working production setup looked like in early 2015.


The Stack (March 2015)

  • React 0.13.0 (released February 2015 - introduced ES6 class components)
  • Webpack 1.7
  • Babel 5 (single package, before the plugins split in Babel 6)
  • webpack-dev-server 1.7
npm init -y
npm install --save react react-dom
npm install --save-dev webpack webpack-dev-server babel-loader babel-core
npm install --save-dev babel-preset-react babel-preset-es2015

webpack.config.js

// webpack.config.js - 2015 production setup
var webpack = require('webpack');
var path    = require('path');

var isProd = process.env.NODE_ENV === 'production';

module.exports = {
  entry: {
    app: isProd
      ? './src/index.js'
      : ['webpack/hot/dev-server', './src/index.js']
  },

  output: {
    path:       path.join(__dirname, 'dist'),
    filename:   'bundle.js',
    publicPath: '/dist/'
  },

  module: {
    loaders: [
      {
        test:    /\.jsx?$/,
        exclude: /node_modules/,
        loader:  'babel',
        query: {
          presets: ['es2015', 'react']
          // In 2015, no 'stage-0' needed - we kept to stable spec
        }
      },
      {
        test:   /\.css$/,
        loader: 'style!css'
        // style-loader + css-loader chained - ! syntax was Webpack 1 convention
      }
    ]
  },

  resolve: {
    extensions: ['', '.js', '.jsx']
    // Empty string required in Webpack 1 to resolve files without extension
  },

  plugins: isProd ? [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: { warnings: false }
    }),
    new webpack.optimize.DedupePlugin()
    // DedupePlugin: remove duplicate modules - useful with React ecosystem
  ] : [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
    // NoErrorsPlugin: don't emit bundles with errors - prevents broken HMR
  ]
};

.babelrc

{
  "presets": ["es2015", "react"],
  "env": {
    "development": {
      "plugins": ["react-transform"],
      "extra": {
        "react-transform": {
          "transforms": [{
            "transform": "react-transform-hmr",
            "imports": ["react"],
            "locals": ["module"]
          }]
        }
      }
    }
  }
}

Additional install for HMR:

npm install --save-dev babel-plugin-react-transform react-transform-hmr

package.json scripts

{
  "scripts": {
    "start": "webpack-dev-server --hot --inline --port 3000",
    "build": "NODE_ENV=production webpack --progress --colors",
    "build:win": "set NODE_ENV=production && webpack --progress --colors"
  }
}

src/index.js - entry point

import React    from 'react';
import ReactDOM from 'react-dom';
import App      from './App';

// React 0.13: ReactDOM was split out from React in this release
// Previously: React.render() - now: ReactDOM.render()
ReactDOM.render(
  <App />,
  document.getElementById('app')
);

src/App.jsx - ES6 class component

import React, { Component } from 'react';

// React 0.13 introduced ES6 class components
// Previously you'd use React.createClass({...})
class App extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    // In 2015: had to bind methods manually
    // Arrow functions in class properties (class fields) didn't exist yet
    this.increment = this.increment.bind(this);
  }

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <div>
        <h1>Count: {this.state.count}</h1>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

export default App;

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>React App 2015</title>
</head>
<body>
  <div id="app"></div>
  <!-- bundle.js served by webpack-dev-server in dev, from /dist/ in prod -->
  <script src="/dist/bundle.js"></script>
</body>
</html>

Common Problems in 2015

Problem: HMR reloads full page instead of component

Cause: react-transform-hmr not applied. Check .babelrc has "env": { "development": {...}} block and NODE_ENV=development is set.

Problem: "Cannot resolve module 'react/lib/ReactMount'"

Cause: react-transform-hmr version mismatch with React 0.13. Fix:

npm install react-transform-hmr@1.0.1

Problem: CSS imported in JS causes "Cannot find module" error

Install missing loaders:

npm install --save-dev style-loader css-loader

Production Build Output (2015)

A typical app at this stage:

  • bundle.js: ~180 KB minified (React 0.13 was ~120 KB alone)
  • Gzipped: ~52 KB
  • Build time: 8-12 seconds on a 2013 MacBook Pro

By comparison, React 18 + modern tooling produces similar gzipped sizes but with far more functionality. The raw numbers haven't changed dramatically - the developer experience has.


What Changed After 2015

In October 2015, Babel 6 broke this config: the single babel-core became a plugin system, requiring explicit preset packages. In February 2016, Webpack 2 beta introduced import() dynamic loading. In July 2016, Create React App eliminated all of this configuration.

The manual setup taught you what CRA hides: chunk splitting, tree shaking, loader chains, plugin order. That knowledge still applies when CRA's defaults aren't enough.


Aunimeda builds modern web frontends - from single-page applications to complex multi-locale sites.

Contact us to discuss your frontend project. See also: Web Development, Corporate Website Development

Read Also

Next.js SSR vs CSR: How We Got 97 on Lighthouse for a High-Traffic Marketplaceaunimeda
Frontend Engineering

Next.js SSR vs CSR: How We Got 97 on Lighthouse for a High-Traffic Marketplace

Why client-side rendering was killing our e-commerce conversion rate, how Next.js SSR and ISR fixed it, and the exact changes that moved us from 41 to 97 on Google Lighthouse.

React vs Angular 2: Why We Chose React for Our SaaS CRM in 2016aunimeda
Frontend Engineering

React vs Angular 2: Why We Chose React for Our SaaS CRM in 2016

After evaluating both for a complex CRM build, we chose React + Redux. The honest comparison: two-way binding vs unidirectional data flow, component philosophy, and what actually mattered at scale.

Transitioning from jQuery Mobile to Modern Frameworks: A Retrospectiveaunimeda
Frontend Engineering

Transitioning from jQuery Mobile to Modern Frameworks: A Retrospective

jQuery Mobile was the king of mobile web in 2011. We built 12 projects on it. By 2014 it was legacy. Here's the honest story of a framework's rise, its design mistakes, and why we moved on.

Need IT development for your business?

We build websites, mobile apps and AI solutions. Free consultation.

Web Development

Get Consultation All articles