Using env vars in Storybook

Originally published Apr 12, 2021·Tagged #javascript, #web-development, #configuration

Storybook is an amazing tool for web developers. I've been using it for years, and it helps me develop faster, catch edge cases sooner, and create reusable components.

As with any tooling, however, in order to get the most value out of it you'll want to pay close attention to how you're setting it up. Recently, I was working on components destined for a Next.js app, and I wanted to use the NEXT_PUBLIC_ environment variables we had defined in our application. However, by default, Storybook only allows you to expose variables prefixed with STORYBOOK_.

Webpack DefinePlugin to the rescue

Thankfully, Storybook's flexible configuration system allows us direct access to Webpack. In order to replicate Next's handling of environment variables, we'll need to do the following:

  1. Load environment variables from .env.local, the way Next.js does
  2. Expose these variables to Webpack

To load the variables, we'll use dotenv. This package is widely used and has broad support. However, it handles .env syntax more permissively than Next does, so be sure to test any config changes in the actual Next app — if it works in Storybook, it's not guaranteed that it'll work in Next.

yarn add -D dotenv

Next, we'll update .storybook/main.js to define the environment variables in which we're interested. If we were interested in specific variables, we could hard-code these into the config. However, to prevent us from having to remember to update this file when we add new variables, we'll iterate over all the variables and select the ones that start with the prefix we want.

const { resolve } = require('path');
const webpack = require('webpack');

module.exports = {
  // ... Other unrelated config ...
  async webpackFinal(config) {
    config.plugins.push(
      new webpack.DefinePlugin(
        Object.keys(process.env)
          .filter((key) => key.startsWith('NEXT_PUBLIC_'))
          .reduce(
            (state, nextKey) => ({ ...state, [nextKey]: process.env[nextKey] }),
            {},
          ),
      ),
    );
    return config;
  }
};

Now, your Storybook will use the same environment variables as your Next.js app. I've used this alongside mock-service-worker to mock out API responses, but I'm sure there are plenty of other uses for this approach.