DEV Community

Cover image for React App From Scratch and Deploy to Heroku
Rodrigo Walter Ehresmann
Rodrigo Walter Ehresmann

Posted on

React App From Scratch and Deploy to Heroku

Tired of using CRA (Create React App) for everything? This post is for you. In this post, we'll set up what could be used as the base for you to create your own React project template, already ready for deploying to Heroku.

Note: If you try to code along with this post, be aware of the packages versions I'm using, though. For reference, the final package.json is listed right after the Conclusion section of this post.

Create your project folder, run npm init --y to create your package.json, and let's start!

React Installation and Package Structure

We'll start installing what we need about react with npm install react react-dom.

Now create two folders:

  • build: Where your built app will be placed;
  • src: Where your actual app components will be placed;
  • public: Where we'll put our static files.

Populate your src folder with two files:

  • App.js, the main component of your application:
import React from "react";

function App() {
  return <div>Hello World!</div>
}

export default App;
Enter fullscreen mode Exit fullscreen mode
  • index.js, the entry point of your application:
import React from "react";
import ReactDom from "react-dom";
import App from "./App";

ReactDom.render(
  <App />,
  document.getElementById("root")
);
Enter fullscreen mode Exit fullscreen mode

Did you notice we need an html element with id root to make this work? Don't worry about it, it's our next step.

Webpack and Project Up!

Webpack is our project bundler, the one responsible for getting our javascript files and making them usable in the browser. Install the necessary webpack packages with npm install webpack webpack-cli --save-dev.

In your project root folder, create the webpack.config.js. This is the basic structure we'll fill:

module.exports = {
  mode: "development",
  entry: "",
  output: {
  },
  plugins: [],
  module: {},
}
Enter fullscreen mode Exit fullscreen mode
  • mode: If you're running in production or development. You can use an ENV variable to determine the environment if you want (e.g., process.env.MY_ENV);
  • entry: The entry point of our application, or in other words, from where the bundler must start looking in order to bundle our application;
  • output: Output interface to configure where the bundled files should be placed;
  • plugins: Plugins interface in case we wanna use any plugin;
  • module: Interface to configure Webpack dependencies if you have any.

Let's start with the module. You probably know about Babel to get browser compatible-javascript, and I wanna make it available through Webpack module. Also, you probably will need some Babel presets (a.k.a., Babel plugins to support particular language features), so install everything with npm install @babel/core babel-loader @babel/preset-env @babel/preset-react --save-dev, and update your webpack module to be like this:

module:{
    rules:[
      {
        test:/\.js$/,
        exclude:/node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env','@babel/preset-react']
          }
        }
      },
    ]
},
Enter fullscreen mode Exit fullscreen mode

I'll not elaborate about these presets and how rules are configurated, you can search more about it if you're interested, and keep in mind those configurations aren't something you need to memorize because you'll usually find them in the npm package description.

Going next for the entry, we simply set it to our application entry point:

...

entry: './src/index.js',

...
Enter fullscreen mode Exit fullscreen mode

For the output we want to have our bundled files to be placed in a single file named bundle.js inside our build folder:

const path = require('path'); // add this at the top of your webpack.config.js
...

  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'build'),
  },

...  
Enter fullscreen mode Exit fullscreen mode

This bundle file will be loaded through a <script> tag in your index.html file. But here is the question: how do we generate this bundle file? First off, let's create our index.html inside our public folder:

<html>
   <head>
      <title>React App From Scratch</title>
   </head>
   <body>
      <div id="root"></div>
   </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Note: it's just a simple html you can update the way you want, just don't forget to keep <div id="root"></div> because is where we specified that the App component will be rendered (back to our index.js).

Now we can add the package npm install html-webpack-plugin --save-dev that will handle the bundle.js generation. Update the plugins with this:

...

const HtmlWebPackPlugin = require("html-webpack-plugin");

...

plugins:[
  new HtmlWebPackPlugin({
    template: path.resolve( __dirname, 'public/index.html' ),
    filename: 'index.html'
  })
],

...
Enter fullscreen mode Exit fullscreen mode

When we build our application, a default index.html will be generated if we use HtmlWebPackPlugin template and filename. We don't wanna have the default one, though, so we're saying to template it with our public/index.html and name it with this same filename.

Before we can run our app, add the server boot script inside scripts of your package.json: "dev": "webpack-dev-server". We also need to add in our webpack.config.js the option indicating we're using a dev server ():

...

devServer: {},

...
Enter fullscreen mode Exit fullscreen mode

For reference, here is the final version of our webpack.config.js and package.json:

const path = require('path');
const HtmlWebPackPlugin = require("html-webpack-plugin");

module.exports = {
  mode: "development",
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'build'),
  },  
  plugins:[
    new HtmlWebPackPlugin({
      template: path.resolve( __dirname, 'public/index.html' ),
      filename: 'index.html'
    })
  ],
  module:{
      rules:[
        {
          test:/\.js$/,
          exclude:/node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env','@babel/preset-react']
            }
          }
        },
      ]
  },
  devServer: {},
}
Enter fullscreen mode Exit fullscreen mode
// package.json
{
  "name": "react-app-from-scratch",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "devDependencies": {
    "@babel/core": "^7.16.7",
    "@babel/preset-env": "^7.16.7",
    "@babel/preset-react": "^7.16.7",
    "babel-loader": "^8.2.3",
    "html-webpack-plugin": "^5.5.0",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.7.2"
  }
}
Enter fullscreen mode Exit fullscreen mode

Just run npm run dev and your server is up! We didn't change the default port of the server, so you'll be able to see our "Hello World!" message accessing http://localhost:8080.

Hello World page

Deploy to Heroku

To deploy to Heroku we need beforehand:

  • A web server: we're essentially serving static files, and Heroku requires a web server. We can create our own webserver to simply serve the static files;
  • npm run build npm run start command: those commands are the default ones Heroku will run to build and start our application.

Run npm install express that we'll use to create our node server. In your root repository create your server.js:

const path = require('path');
const express = require('express');

const app = express();

app.use(express.static(path.join(__dirname, 'build')));
app.set('port', process.env.PORT || 3000);

const server = app.listen(app.get('port'), function() {
  console.log('listening on port ', server.address().port);
});
Enter fullscreen mode Exit fullscreen mode

This is the most basic node server snippet you'll find over the internet, and the magic happens here app.use(express.static(path.join(__dirname, 'build'))): we're saying that our built app is inside the build folder, and those files should be served as static.

Now we finish adding the build and start scripts in our package.json:

{
  "name": "react-app-from-scratch",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server",
    "start": "node server.js",
    "build": "webpack --config ./webpack.config.js --mode='production'"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "express": "^4.17.2",
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "devDependencies": {
    "@babel/core": "^7.16.7",
    "@babel/preset-env": "^7.16.7",
    "@babel/preset-react": "^7.16.7",
    "babel-loader": "^8.2.3",
    "html-webpack-plugin": "^5.5.0",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.7.2"
  }
}
Enter fullscreen mode Exit fullscreen mode

You can test if everything is working properly running your newly added scripts: npm run build and npm run start. You should be able to see our "Hello, World!" message again, now on http://localhost:3000.

And this is it, you are ready to deploy to Heroku!

Note: Just ensure yourself that heroku/nodejs buildpack is present in your Heroku app. Probably is, because already comes by default when you create your app!

Conclusion

This post shows how you can create your React app by scratch. It was explained the basics of the webpack configuration file, and how you can create your webserver to deploy your application to Heroku.

Bear in mind that those configurations don't deliver you a template for a React app ready for production, there's a lot more to be done before you can say this, like server routing, code splitting, etc. But with what it's presented here you have a start point in order to start building your own template, in case you just don't wanna use something like CRA.


This is it! If you have any comments or suggestions, don't hold back, let me know.


Options if you like my content and would like to support me directly (never required, but much appreciated):

BTC address: bc1q5l93xue3hxrrwdjxcqyjhaxfw6vz0ycdw2sg06

buy me a coffee

Top comments (0)