🥺

Next.jsのサブパスからStorybookを公開する

2021/10/12に公開

Next.jsのサブパスからStorybookを公開する際につまづいたポイントが何点かあったので、メモとして残しておく。
(ZennのStorybook公開してくれないかな・・・)

動作環境

Next.js 11.1.2
Storybook 6.3.10

Storybookからアセットを参照出来るようにする

サブパスで公開するとアセットのリンク切れが発生するので、ビルド時にリンクのプレフィックスを指定出来るようにする。

./storybook/main.js
const { merge } = require('webpack-merge');

const prefix = process.env.STORYBOOK_PREFIX ? `/${process.env.STORYBOOK_PREFIX}` : '';

module.exports = {
  ...
  managerHead: (head) => {
    return `
      ${head}
      <link rel="shortcut icon" type="image/x-icon" href="${prefix}/favicon.ico">
      <script>
        window['PREVIEW_URL'] = '${prefix}/iframe.html';
      </script>
    `;
  },
  webpackFinal: async (config) => {
    return merge(config, {
      output: {
        publicPath: `${prefix}/`,
      },
    });
  },
  managerWebpack: async (config) => {
    return merge(config, {
      output: {
        publicPath: `${prefix}/`,
      },
    });
  },
};

Storybookのビルドスクリプトを書き換える

ビルド時にSTORYBOOK_PREFIXへ任意の値を指定し、ビルド出力先をNext.jsのpublicフォルダ配下へと変更する。

  "scripts": {
-    "build:storybook": "build-storybook",
+    "build:storybook": "STORYBOOK_PREFIX=storybook build-storybook -o ./public/storybook",
  },

Next.jsのrewritesを設定する

このままだとindex.htmlまで入力しないと表示出来ないので、rewritesで参照先を変更する。

next.config.js
module.exports = {
  ...
  rewrites: async () => {
    return [
      {
        source: '/storybook',
        destination: '/storybook/index.html',
      },
    ];
  },
};

Next.jsのImageコンポーネントのモックを定義する

Next.jsのImageコンポーネントを使用しているとStorybookでエラーが発生する為、imgタグと置き換えるようなモックを定義する必要がある。

.storybook/preview.js
import * as Image from 'next/image';

Object.defineProperty(Image, 'default', {
  configurable: true,
  value: props => <img {...props} />
});

TypeScriptの絶対インポートをStorybookに認識させる

TypeScriptで絶対インポートを使用しているとStorybookでモジュール解決が出来なくなってしまうので、webpackFinalに別途設定する必要がある。

./storybook/main.js
const path = require('path');
const { merge } = require('webpack-merge');

const getBaseDir = () => {
  const tsconfig = require('../tsconfig.json');
  return path.resolve(process.cwd(), tsconfig.compilerOptions.baseUrl);
};

module.exports = {
  ...
  webpackFinal: async (config) => {
    return merge(config, {
      resolve: {
        modules: [getBaseDir()],
      },
    });
  },
};

Discussion