storybookjs / storybook

Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
https://storybook.js.org
MIT License
83.49k stars 9.13k forks source link

[BUG] NextJS addon does not get environment variables by default #26215

Open SalahAdDin opened 4 months ago

SalahAdDin commented 4 months ago

Describe the bug

We use the environment variables to set up among others, API url, base url, etc. But when launching the storybook it is unable to find the setup environment variables!

To Reproduce

Just setup and NextJS project with storybook, define any page or function to use with environment variables, create its story and run it.

System

Storybook Environment Info:

  System:
    OS: Linux 6.1 Manjaro Linux
    CPU: (16) x64 AMD Ryzen 9 5900HX with Radeon Graphics
    Shell: 5.2.26 - /bin/bash
  Binaries:
    Node: 20.11.0 - ~/.nvm/versions/node/v20.11.0/bin/node
    Yarn: 1.22.21 - /usr/bin/yarn
    npm: 10.2.4 - ~/.nvm/versions/node/v20.11.0/bin/npm
    pnpm: 8.15.4 - /usr/bin/pnpm <----- active
  npmPackages:
    @storybook/addon-a11y: 7.6.17 => 7.6.17 
    @storybook/addon-essentials: 7.6.17 => 7.6.17 
    @storybook/addon-interactions: 7.6.17 => 7.6.17 
    @storybook/addon-links: 7.6.17 => 7.6.17 
    @storybook/addon-onboarding: 1.0.11 => 1.0.11 
    @storybook/addon-storysource: 7.6.17 => 7.6.17 
    @storybook/addon-themes: 7.6.17 => 7.6.17 
    @storybook/blocks: 7.6.17 => 7.6.17 
    @storybook/nextjs: 7.6.17 => 7.6.17 
    @storybook/react: 7.6.17 => 7.6.17 
    @storybook/test: 8.0.0-alpha.16 => 8.0.0-alpha.16 
    @storybook/theming: 7.6.17 => 7.6.17 
    eslint-plugin-storybook: 0.8.0 => 0.8.0 
    msw-storybook-addon: 2.0.0-beta.1 => 2.0.0-beta.1 
    storybook: 7.6.17 => 7.6.17 
    storybook-addon-pseudo-states: 2.1.2 => 2.1.2 
    storybook-addon-rtl: 1.0.0 => 1.0.0

Additional context

We are using Storybook as a workaround to test the pages with MSW, using the respective addon.

It could be fixed by applying this on the addon.

shilman commented 4 months ago

cc @valentinpalkovic

valentinpalkovic commented 4 months ago

@SalahAdDin Have you followed the official documentation about setting up environment variables in Storybook?: https://storybook.js.org/docs/configure/environment-variables#using-storybook-configuration

SalahAdDin commented 4 months ago

@SalahAdDin Have you followed the official documentation about setting up environment variables in Storybook?: https://storybook.js.org/docs/configure/environment-variables#using-storybook-configuration

The thing is, if we are working with NextJS, it should not require extra configuration for environment variables.

valentinpalkovic commented 4 months ago

Storybook isn't an integration into Next.js, but rather it runs as a separate App. Our APIs are mainly designed in a way to be usable across different kind of frameworks and renderers. Automatically loading your environment variables or even expose process.env to the browser automatically can be seriously dangerous. For sure, we could do whatever Next.js is doing behind the scenes to provide environment variables to the client and server bundle by following specific rules, but since an universal API to support envs already exists in Storybook and it is pretty trivial to set it up (usually did once), I don’t think we will support automatic detection of env vars for Next.js

SalahAdDin commented 4 months ago

@valentinpalkovic OK, I tried to mock some of the NextJS required environment variables as the documentation says:

  env: (config) => ({
    ...config,
    NEXT_PUBLIC_API_PORT: "6006",
  }),

But it always returns undefined, and it is problematic.

Even this does not work:

Additional context

We are using Storybook as a workaround to test the pages with MSW, using the respective addon.

It could be fixed by applying this on the addon.

jflheureux commented 1 month ago

I have not tried the solution mentioned in the issue. With recent Storybook main.js/ts file, it would look more like this now:

import type { StorybookConfig } from '@storybook/nextjs';
import webpack from 'webpack';

const config: StorybookConfig = {
  // All of your existing Storybook config
  webpackFinal: async (config) => {
    if (!config?.plugins) {
      return 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;
  },
};
export default config;
SalahAdDin commented 1 month ago

I have not tried the solution mentioned in the issue. With recent Storybook main.js/ts file, it would look more like this now:

import type { StorybookConfig } from '@storybook/nextjs';
import webpack from 'webpack';

const config: StorybookConfig = {
  // All of your existing Storybook config
  webpackFinal: async (config) => {
    if (!config?.plugins) {
      return 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;
  },
};
export default config;

Does it work?

jflheureux commented 1 month ago

One of my colleague just tested and this works:

  webpackFinal: async (config: Configuration) => {
    config.plugins = config.plugins || [];

    config.plugins.push(
      new DefinePlugin(
        Object.keys(process.env)
          .filter((key) => key.startsWith('NEXT_PUBLIC_'))
          .reduce(
            (acc, key) => ({
              ...acc,
              [`process.env.${key}`]: JSON.stringify(process.env[key]),
            }),
            {}
          )
      )
    );

    return config;
  },
SalahAdDin commented 2 weeks ago

One of my colleague just tested and this works:

  webpackFinal: async (config: Configuration) => {
    config.plugins = config.plugins || [];

    config.plugins.push(
      new DefinePlugin(
        Object.keys(process.env)
          .filter((key) => key.startsWith('NEXT_PUBLIC_'))
          .reduce(
            (acc, key) => ({
              ...acc,
              [`process.env.${key}`]: JSON.stringify(process.env[key]),
            }),
            {}
          )
      )
    );

    return config;
  },

How do you manage the types? Where is the DefinePlugin type?

shilman commented 2 weeks ago

@valentinpalkovic I think this is something we can support easily in @storybook/next and we might as well to reduce friction for users. WDYT?

jflheureux commented 2 weeks ago

@SalahAdDin Here are the imports for TypeScript and the code:

import type { StorybookConfig } from '@storybook/nextjs';
import { Configuration } from 'webpack';
const { DefinePlugin } = require('webpack');

const config: StorybookConfig = {
  // The rest of your configuration goes here...
  // Needed to add this so that the NEXT_PUBLIC_ env variables are available in Storybook
  webpackFinal: async (config: Configuration) => {
    config.plugins = config.plugins || [];

    config.plugins.push(
      new DefinePlugin(
        Object.keys(process.env)
          .filter((key) => key.startsWith('NEXT_PUBLIC_'))
          .reduce(
            (acc, key) => ({
              ...acc,
              [`process.env.${key}`]: JSON.stringify(process.env[key]),
            }),
            {}
          )
      )
    );

    return config;
  },
};
export default config;
SalahAdDin commented 2 weeks ago
const { DefinePlugin } = require('webpack');

It requires to install the Webpack types from DefinitelyTyped, right?

jflheureux commented 1 week ago

I do not know where 'webpack' comes from as it is not in your package.json dependencies. It must be a dependency of Storybook. For me, webpack is located at /myproject/node_modules/webpack/types I also guess this import code would work just as fine: import { Configuration, DefinePlugin } from 'webpack';

SalahAdDin commented 5 days ago

I do not know where 'webpack' comes from as it is not in your package.json dependencies. It must be a dependency of Storybook. For me, webpack is located at /myproject/node_modules/webpack/types I also guess this import code would work just as fine: import { Configuration, DefinePlugin } from 'webpack';

image

We are getting this issue. @jflheureux @shilman