Using Redux DevTools in production

TL;DR: Allowing Redux DevTools Extension in production has lots of benefits and no harm. To make it even better, now you can configure which features to allow. In addition, we’ve released an npm package to be used in production.

I’ve seen lots of tweets warning that sites like Pinterest, Intercom, Bitbucket, Flipkart and others left Redux DevTools in production. However, there’s nothing wrong here. The misconception is that they didn’t include Redux DevTools in the production bundle, but just allowed the browser extension to be used there. The difference between those two is signifiant.

We should have access to the data we owe

Shouldn’t we trust our user to have access to his data? Inspect the data with Redux DevTools Extension while choosing an Asus motherboard for your PC, exploring your favorite products on ProductHunt, planning your todos on Todoist, designing your sketches in Autodesk, searching for a city to build your team on Teleport, managing your WordPress.com sites, tracking Apollo GraphQL queries or exploring the ASTs on ASTExplorer.

You could even debug Firefox debugger or Hyper terminal (though it’s not that simple for desktop apps):

How that could be useful

Once I was submitting a form on OpenCollective and, for some reason, the Save button had no effect. First thought was that it’s a backend issue, but there were no network requests on submit. Hopefully, Redux Extension is enabled there, and I was able to get the issue right away:

Notice that the page in the screenshot above has an error notification. It warns that the description I entered is longer than 100 characters, but it’s behind the header’s layer. We could endlessly debug that, or they’d just lose one(?) user.

How that helps devs

Even though it aims to be a tool for the development environment, Redux DevTools Extension has lots of benefits in production:

  • Get the detailed image of what is happening in your production app;
  • Export the state history right from production into development;
  • Generate tests for bugs caught in production;
  • Replicate bugs that don’t occur in development;
  • Dispatch actions remotely.

What about perf and security

Assuming that usually end-users don’t have the extension installed, it wouldn’t have any affect on the most of them. It does affect the devs, but only when it’s opened. When not in use, only the instrumentation enhancer stores the history of all dispatched actions to be sent to the extension. The RAM consumption is optimized by the limit of actions to be stored, which is `50` by default. You can disable recording any actions by setting shouldRecord option to false (it’s true by default), then it will not store the history till clicking the record button in the monitor window.

Letting the users, who installed the extension, to see how their data is structured inside the store, isn’t a privacy issue. Anyway, application store is usually public even without including the extension’s enhancer.

You shouldn’t store sensitive data inside the states. If so, use actionSanitizer / stateSanitizer options. It’s also useful to strip huge payloads included in the action or state objects (for example, an uploaded image blob).

Thanks to Redux architecture, we are not exposing the store object globally in order to hook it into the extension. In order for a malefic script to get access to your store, it has to replace the extension’s function to act as a store enhancer, but only before the store is created (which usually is not the case of XSS injections). Theoretically, a malefic script can listen the messages sent to the extension, but, if so, it could sniff the app network requests as well.

Of course you you want to have 100% guarantee that it’s safe. In some cases you might be suspicious not only about bad guys there, but about yourself as well. Say you was jumping inside the history of actions (time travelling), and that invoked a side effect (though it shouldn’t), which could change some data about the current user in the production database.

You might want a restricted version of the extension

For most of cases it’s not a problem to include the extension as is:

import { createStore, applyMiddleware } from 'redux';
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// options like actionSanitizer, stateSanitizer
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(...middleware),
// other store enhancers if any
);
const store = createStore(reducer, enhancer);

Or with the npm package:

import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
const composeEnhancers = composeWithDevTools({
// options like actionSanitizer, stateSanitizer
});
const store = createStore(reducer, composeEnhancers(
applyMiddleware(...middleware),
// other store enhancers if any
));

If you want to restrict the extension, specify the features to allow:

const composeEnhancers = composeWithDevTools({ 
// or window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
features: {
pause: true, // start/pause recording of dispatched actions
lock: true, // lock/unlock dispatching actions and side effects
persist: true, // persist states on page reloading
export: true, // export history of actions in a file
import: 'custom', // import history of actions from a file
jump: true, // jump back and forth (time travelling)
skip: true, // skip (cancel) actions
reorder: true, // drag and drop actions in the history list
dispatch: true, // dispatch custom actions or action creators
test: true // generate tests for the selected actions
},
// other options like actionSanitizer, stateSanitizer
});

By default all of them are enabled, unless features parameter is specified.

To be more restrictive and to get even more of security and performance, we’re providing redux-devtools-extension/logOnly, used the same as redux-devtools-extension module above:

import { composeWithDevTools } from 'redux-devtools-extension/logOnly';
const composeEnhancers = composeWithDevTools({
// options like actionSanitizer, stateSanitizer
});
const store = createStore(reducer, composeEnhancers(
applyMiddleware(...middleware),
// other store enhancers if any
));

The difference is that it will not include the instrumentation enhancer, so will not affect the performance and will not store anything if not required. It’s minimalistic and restricted to logging, exporting and generating tests only. Moreover, it auto pauses when the extension’s window is not opened, and so has zero impact on your app when not in use. The module adds just few lines of code and uses extension’s API directly to send data.

The best practice is to use full featured version for development and the restricted one for production, so we’ll get the best of both worlds: DX and perf. As React already does, check process.env.NODE_ENV:

To make it even simpler, we’re providing redux-devtools-extension/logOnlyInProduction helper. It will include the necessary part according to your process.env.NODE_ENV, and Webpack/UglifyJS will strip the other. However, don’t forget to add 'process.env.NODE_ENV': JSON.stringify('production') for the production bundle (as in the tweet above). If use react-create-app, it does that for you.

What if we want the end-users to help the debugging

To get reports from your end-users in production, use redux-remotedev enhancer. It’s standalone, so your users don’t need to install the extension to provide more details about an issue there.

When a specific action is fired (for example ‘SEND_REPORT’), it will send the history to the backend. After that, it will generate an url to get the report right into the extension. But that’s a topic for the next post.


If you’re using the extension in production, let us know in the comments. Click the green heart bellow if you liked the article. Follow me on twitter at @mdiordiev and watch the extension’s repo to not miss any updates.

Happy debugging!