How to develop React apps faster with Neutrino JS

Modern JavaScript workflows are complicated, especially with frameworks like React. You have transpilation, module bundling, asset management, development servers and more just to get a “Hello World” page off the ground. If you're setting all of that up yourself for every project, you could be saving a lot of time and pulled hair by using a build framework instead.

A what?

React build frameworks abstract away the complexity of build tooling like Webpack and Babel, providing a simple, unified workflow for developers. They allow you to centralize configuration, patch holes, upgrade and coordinate dependencies and lean on a whole community worth of optimization and improvements across a range of projects by just updating your framework version.

The tradeoff is flexibility and we'll touch on ways around this in a moment. But we're strong believers that anything not core to the value of your product should be abstracted away, so that you can focus on what matters - providing real value to your users or audience. Configuring build tooling and infrastructure certainly falls under that umbrella and unless you absolutely need fine-grained control over every step of configuration, there are solutions you can lean on.

Sounds great, what are my options?

So glad you asked! By far the most popular and well-known React boilerplate is create-react-app (CRA). Developed by the core Facebook team, CRA has long been the standard when scaffolding out a new React app. But there's a problem. CRA gives you two options: you can either run with the (extensive) configuration it comes pre-bundled with, or you can 'eject' your app (unbundling the build framework pages into your project) and configure everything manually yourself.

The idea is that you can develop your app without touching build configuration at all, and if you run up against something CRA can't handle, you simply eject and tweak configs. The issue is that as soon as you eject from CRA, you lose every benefit of using a build framework. No more centralised configuration, no more easy dependency updates, no further optimisation to lean on. Surely there's a middle ground?

This is where Neutrino comes in. Originally developed by Mozilla, Neutrino is a collection of configuration presets built on top of a powerful, abstracted API for common build tools. What this means in practice is that out of the box you can have a zero-touch setup similar to CRA and other boilerplates, but if you run up against an edge case that the default React preset doesn't cover, you can mix in other presets or tweak the config chain without ejecting your whole app. Neutrino is also framework agnostic, so you can carry it across React, Vue, Node, and other projects.

How do I use this magic?

The fastest way to get started with Neutrino is to use their create-project CLI tool. Run it with NPM's built-in npx utility, and follow the prompts to scaffold a React app:

npx @neutrinojs/create-project <directory-name>

This will setup a standard folder structure with src and build directories. You will have the option to scaffold a testing suite with test runners like Jest, Karma, or Mocha. You also have the option to scaffold linting presets with ESlint as well, such as StandardJS or AirBnB's styleguide.

That's it, you're done! Run npm run start to spin up a development server, and npm run build to create a production-ready build.

Sounds easy enough. Can I customise how Neutrino works?

You can! All of Neutrino's configuration is done in a .neutrinorc.js file in the root of your project. Build tool specific config files, like webpack.config.js, only consume the configuration generated by Neutrino. Basic customization can be done by providing options to Neutrino and its' React preset:

const react = require('@neutrinojs/react');
 
module.exports = {
  // Customise directories
  options: {
    source: 'src',
    output: 'dist',
  },
  use: [
    react({
      // Control Hot Module Replacement
      hot: false,
 
      // Control webpack's `output.publicPath` setting.
      publicPath: '/',
 
      // Change the default page title
      html: {
        title: 'My React App',
      },
 
      // Target specific browser support
      targets: {
        browsers: ['last 1 Chrome versions', 'last 1 Firefox versions'],
      },
 
      // Add additional Babel plugins, presets, or env options
      babel: {
        presets: ['@babel/preset-typescript'],
        plugins: ['react-css-modules'],
      },
    }),
  ],
};

For more advanced customisation, you can take advantage of Neutrino's chaining API (webpack-chain) directly. It allows you to modify existing Webpack rules, create custom rules, add new loaders, define plugins, and more without having to deal with underlying configuration directly or eject from Neutrino. For example, adding a custom plugin to your config would look like this:

module.exports = (neutrino) => {
  neutrino.config.plugin('clean').use(CleanPlugin);
};

While a full overview of the API is beyond the scope of our little article, we highly encourage you to check out the official documentation.

Tell us about your project

Get in touch