ゲーム開発
Unity
UnrealEngine
C++
Blender
Houdini
ゲーム数学
ゲームAI
グラフィックス
サウンド
アニメーション
GBDK
制作日記
IT関連
ツール開発
フロントエンド関連
サーバサイド関連
WordPress関連
ソフトウェア設計
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
ラーメン日記
四コマ漫画
その他
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • Blender
    • Houdini
    • ゲーム数学
    • ゲームAI
    • グラフィックス
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • IT関連
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • WordPress関連
    • ソフトウェア設計
    • おすすめ技術書
  • 音楽
    • DTM
    • 楽器・機材
    • ピアノ
  • ラーメン日記
    • 四コマ漫画
      • その他
        • おすすめアイテム
        • おもしろコラム
      1. ホーム
      2. 20231231_01_app_design

      【Next.js】第一回 WordPressブログをNext.jsに移行する 〜全体設計、環境構築編〜

      Next.jsReactJavaScriptWordPress関連フロントエンド関連SSGStorybookEmotionNode.jsp5.js
      2023-12-31

      マイケル
      マイケル
      みなさんこんにちは! マイケルです!
      エレキベア
      エレキベア
      こんにちクマ〜〜
      マイケル
      マイケル
      この度、WordPress製だった当ブログをNext.jsで作り直しました! そのため三記事ほどに分けて、実際に開発した内容を紹介していきます。
      【都会のエレキベア】ブログを大幅リニューアル!WordPressからNext.jsに移行するまでの流れをまとめる
      2024-01-01
      【Next.js】第一回 WordPressブログをNext.jsに移行する 〜全体設計、環境構築編〜
      2023-12-31
      【Next.js】第二回 WordPressブログをNext.jsに移行する 〜WordPressデータの移行・表示編〜
      2023-12-31
      【Next.js】第三回 WordPressブログをNext.jsに移行する 〜Markdown執筆環境構築編〜
      2023-12-31
      【Next.js】第四回 WordPressブログをNext.jsに移行する 〜サーバ移行・SEO・広告設定編〜
      2023-12-31
      エレキベア
      エレキベア
      おめでたいクマ〜〜〜
      マイケル
      マイケル
      今回は第一回ということで「全体設計、環境構築」編 になります! コードについても公開できる範囲でGitHubに上げているので、同じようにNext.jsに移行したと考えている方はご参考ください!

      GitHub - nextjs-elekibear-blog-scripts

      エレキベア
      エレキベア
      中々ボリュームがあるクマね・・・

      参考書籍

      マイケル
      マイケル
      Next.jsの学習をするにあたり、下記書籍を参考にさせていただきました!

      TypeScriptとReact/Next.jsでつくる実践Webアプリケーション

      マイケル
      マイケル
      前半のJavaScriptの歴史〜React、Next.jsの解説が非常に分かりやすかったです! Next.js12使用でPageRouterでの記載にはなっていますが、本格的にアプリ開発したい方にはおすすめです!
      エレキベア
      エレキベア
      最近のWeb技術をキャッチアップしたい方にはおすすめクマね

      全体設計

      マイケル
      マイケル
      まずはWordPressブログを移行するにあたり、 どのような設計にしたか?について触れていきます。

      SSGのみで構成したい

      マイケル
      マイケル
      ベースとなる考えとして、DBサーバは使用せずにSSGのみで構成したい というものがあったため、フレームワークは Next.jsを使用 することにしました。 SSGは 静的サイトジェネレータ の略で、 デプロイする前に事前にデータフェッチして静的サイトを生成しておく手法 になります。
      20231231_next_app_design_09
      ▲静的サイトジェネレータのイメージ

      マイケル
      マイケル
      静的サイト化することで、 ・閲覧時のサーバ通信が発生しないためレスポンスが高速になる といった大きなメリットがあります。 他の主要なレンダリング手法としては下記のようなものがあります。
      フロントエンドのレンダリング手法
      名称
      概要
      特徴
      SSG
      静的サイトジェネレータ
      事前にデータを取得して静的ファイルを生成する。
      初期描画、レスポンスが高速だが、データのリアルタイム性が求められるコンテンツには適さない。
      CSR
      クライアントサイドレンダリング
      ブラウザで初期描画した後に非同期でデータ取得して描画する。
      初期描画が遅いが、リアルタイム性が重要なページに適している。
      SSR
      サーバサイドレンダリング
      ページへのアクセス毎にサーバ側で描画してクライアントへ渡す。
      常に最新のデータを扱うことが出来るが、レスポンスが遅い。
      ISR
      インクリメンタル静的再生成
      事前にページを生成しつつ。アクセスに応じて再度ページを生成し直す。(SSGの応用)
      SSGとSSRの中間のような特徴がある。
      エレキベア
      エレキベア
      なるほどクマ データをリアルタイムにやり取りしない限りはなるべくSSGにするのがよさそうくまね
      マイケル
      マイケル
      当サイト含む個人ブログに関しては更新頻度が頻繁にあるわけではなくリアルタイム性も求められないため、SSGの恩恵をフルに受けられるというわけです。 今回開発するページは下記二つのため、どちらもSSGで対応できそうです。
      開発するページの種類
      URLパス
      ページ
      /
      記事一覧ページ
      /post/[slug]
      記事ページ
      マイケル
      マイケル
      また、静的サイトであれば無料で公開できるホスティングサービスもあるため サーバ費用が抑えられるのも大きなメリットです。 今回は、2024/1現在で商用利用も可能な Netlify を使用することにしました。

      Netlify 公式ページ

      エレキベア
      エレキベア
      サーバ費用も実質無料にできるということクマか・・・ 恐ろしいクマ・・・

      画像データは専用サーバに移行

      マイケル
      マイケル
      NetlifyへのデプロイはGitHubリポジトリと連携するのを想定しているのですが、 画像ファイルについては容量が大きいため、その内リポジトリで管理しきれなくなる可能性があるのではないか?という懸念がありました。
      エレキベア
      エレキベア
      確かに現状で200記事以上あるクマからね・・・
      マイケル
      マイケル
      そのため、画像ファイル類はアプリケーションとは別のサーバで管理することにしました。 そちらもNetlifyで別途サーバを立ててデプロイしています。
      デプロイするサーバ
      種類
      ドメイン
      アプリケーション
      elekibear.com(wp-next-eleikibear.netlify.app)
      画像ファイル類
      wp-next-elekibear-content.netlify.app
      エレキベア
      エレキベア
      Netlifyフル活用クマ〜〜〜
      マイケル
      マイケル
      まあNetlifyでも耐えきれない時が来るかもしれないし、 また後々サーバ移行する自体になった時にも分けておいた方が対応しやすそうだね
      ※追記※
      Netlifyサーバでの運用では、Bandwidth(帯域幅)の領域が100GBギリギリとなってしまったため、 現在はVPSサーバを用意してそちらに格納する運用にしています。

      既存記事データとMarkdownデータの棲み分け

      マイケル
      マイケル
      そして記事データの管理方法についてですが、既存のWordPressデータに関しては必要なデータをCSV(+記事テキスト)として出力 して参照するようにしました。 DBサーバは完全に使用しない方向で、またローカルでもデータベース管理するほどではないと判断したためです。
      マイケル
      マイケル
      そしてやはり今後の記事執筆に関してはMarkdownを使用したいため 下記のような形で既存データと今後のデータで分けて管理するようにしました。
      20231231_next_app_design_01
      ▲既存のWordPressデータはエクスポートして表示し、今後はMarkdownで執筆できるようにする

      エレキベア
      エレキベア
      執筆作業はその方が絶対に効率いいクマね

      全体のフォルダ構成

      マイケル
      マイケル
      以上を踏まえて、下記のようなフォルダ構成で管理することにしました。 dataフォルダ内にマスタデータ(CSV)、記事データ(Text、Markdown)を格納しています。
      .
      ├── data
      │   ├── db
      │   ├── posts
      │   └── posts-md
      │       └── preview
      ├── public
      │   ├── dummy
      │   ├── favicon
      │   ├── img
      │   └── lib
      ├── src
      │   ├── api
      │   ├── common
      │   ├── components
      │   ├── pages
      │   ├── parser
      │   ├── settings
      │   ├── style
      │   ├── theme
      │   └── types
      └── tools
      
      全体のフォルダ構成
      フォルダ名
      内容
      data
      マスタデータ(CSV)、記事ファイル(Text or Markdown)
      public
      画像や外部スクリプトファイル等
      src
      スクリプト
      tools
      開発や記事執筆に使用するツール群
      src配下のフォルダ構成
      フォルダ名
      内容
      api
      データ操作関連の処理(CSV、Markdown)
      common
      スクリプト全体の共通処理
      components
      UIコンポーネント群
      pages
      ページ起点
      parser
      データ変換関連の処理(Markdown)
      settings
      設定ファイル
      style
      スクリプト全体の共通style群
      theme
      Emotionスタイルテーマ
      types
      型定義ファイル
      エレキベア
      エレキベア
      これならいい感じに整理できそうクマね

      使用ライブラリの選定

      使用ライブラリ

      マイケル
      マイケル
      使用した主なライブラリについては下記になります。
      該当箇所
      ライブラリ
      フレームワーク
      Next.js (※PageRouterを使用)
      スタイリング
      Emotion (※CSSPropsを使用)
      開発環境
      ESLint、Prettier、Storybook
      記事ファイル変換
      テキスト記事:cheerio
      Markdown記事:react-markdown、gray-matter、rehype-raw
      記事コンテンツ
      Prism.js、MathJax、Codepen、X
      フレームワーク
      マイケル
      マイケル
      フレームワークは大定番のNext.js! 理由は先ほど述べた通り、SSGで構成したいかつReact実装を経験したかったためです。
      20231231_next_app_design_10

      Next.js 公式ページ

      エレキベア
      エレキベア
      今は定番になりつつあるクマから、情報量も多くて作りやすそうクマね
      マイケル
      マイケル
      ただ、今回の開発ではNext.js 13.4から推奨となったAppRouterは使用せず、 従来のPageRouterを使用する方針で開発を進めました。 Next.js自体触るのが初めてだったため、下記理由によりまずは開発スピード優先で進めることにしたためです。
      • 従来の形式と設計が大きく変わったため、未対応のライブラリも多い
      • 現時点でPageRouterで実装した情報の方が多い

      Nextjs - App Router

      エレキベア
      エレキベア
      まあPageRouterも一度は触っておきたいクマね
      マイケル
      マイケル
      とはいえAppRouterは今後スタンダードになっていくと思われるため、 その内Next.jsのアップデートと合わせて移行対応も行ってみようかと思っています。
      スタイリング
      マイケル
      マイケル
      スタイリング手法としては、CSS-in-JS である Emotion を使用することにしました。 選定した理由としては下記になります。
      • CSS-in-JSの使用により、コンポーネント単位のファイルをまとめたい
      • EmotionのCSSPropを使用することで、素のCSSに近い形で実装したい
      • グローバルCSS、テーマ機能、パラメータによる動的指定など、必要な機能が揃っている
      マイケル
      マイケル
      同じCSS-in-JSであるstyled-componentでなくEmotionを選んだ理由としては二つ目の「CSSPropを使用したい」という理由が大きいです。 JSファイル内に実装しつつもCSS、JSXが素の状態に近いため、後々ライブラリを移行する必要が出てきても棄てやすいと判断したためです。
      20231231_next_app_design_11

      Emotion - css prop

      エレキベア
      エレキベア
      棄てやすさは大事クマ・・・
      マイケル
      マイケル
      現にAppRouterに対応していないようなので、もうしばし様子を見て対応されないようであれば移行を検討する可能性も高いですね・・・
      マイケル
      マイケル
      ちなみに同様の理由でtailwindCSSやBootStrapといったCSSライブラリも一切使用せずEmotionのCSSPropによるCSS実装のみで完結させています。 tailwindに関しては複雑な画面を組むとごちゃごちゃになるし、ライブラリへの依存も強い気がしますね・・・
      エレキベア
      エレキベア
      確かに便利クマが、一度使うと抜けられない危険はありそうクマね
      開発環境
      マイケル
      マイケル
      開発環境としては、コード整形ツールとしてESLintとPrettier、 UIカタログツールとしてStorybookを導入しました。
      エレキベア
      エレキベア
      ESLint、Prettierは定番クマね Storybookは何に使うクマ?
      マイケル
      マイケル
      StorybookはUIコンポーネント単位でカタログ表示・管理ができるツールなんだ。 コンポーネントからボトムアップで開発したり、バリエーションを確認するのに役立ちます。
      20231231_next_app_design_05

      Storybook 公式ページ

      エレキベア
      エレキベア
      なるほどクマ これはReactと相性がよさそうクマね
      記事ファイル変換
      マイケル
      マイケル
      記事データを操作、変換するライブラリとして、 既存のテキストファイルはcheerio、 MarkdownファイルはReactMarkdownとgray-matterを使用することにしました。
      エレキベア
      エレキベア
      変換処理もいろいろカスタマイズが必要そうクマね
      マイケル
      マイケル
      このあたりは第二回、第三回で使用例を解説するため今回は省略します。
      記事コンテンツ
      マイケル
      マイケル
      最後に、記事コンテンツ内で使用しているライブラリとして下記があります。
      ライブラリ名
      用途
      Prism.js
      コード表示
      MathJax
      数式表示
      Codepen
      コード結果表示
      X
      Xポスト表示
      エレキベア
      エレキベア
      いつもお世話になってる機能クマね
      マイケル
      マイケル
      この辺りは外部スクリプトとして読み込むようにしています。 記事の内容に適用する関係でNext/Scriptではなく記事表示時に動的に読み込むよう実装したのですが、この辺りも次回以降解説させてください。

      全体のレイアウトを作成する

      マイケル
      マイケル
      それでは以上のライブラリを使用しながら、下記のような簡単な2カラムレイアウトを作ってみます。
      20231231_next_app_design_07
      ▲よくある2カラムレイアウト

      20231231_next_app_design_08
      ▲一応レスポンシブ

      エレキベア
      エレキベア
      このブログサイトの構成に近いクマね
      マイケル
      マイケル
      環境構築から開発のイメージまでざっくり知っていただければと思います。 今回作るサンプルはGitHubにも上げていますので、よければご参照ください。

      GitHub - nextjs-emotion-layout-sample

      Next.jsプロジェクトの作成

      マイケル
      マイケル
      まずはcreate-next-appを実行してNext.jsのプロジェクトを作成します。 オプションは「use `src/` directory」以外は No を選択して、AppRouterではなくPageRouterを使用するようにします。
      # Next.jsのプロジェクト作成
      # * オプションは「use `src/` directory」以外は No を選択
      # * AppRouterは使用せず、従来のPageRouterを使用する
      $ npx create-next-app@latest --ts
      ✔ What is your project named? … nextjs-emotion-layout-sample
      ✔ Would you like to use ESLint? … No / Yes
      ✔ Would you like to use Tailwind CSS? … No / Yes
      ✔ Would you like to use `src/` directory? … No / Yes
      ✔ Would you like to use App Router? (recommended) … No / Yes
      ✔ Would you like to customize the default import alias? … No / Yes
      
      エレキベア
      エレキベア
      ボイラーテンプレートがあるのはありがたいクマね
      マイケル
      マイケル
      作成後に プロジェクトフォルダ直下でnpm run devを実行、localhost:3000にアクセスするとサンプル画面が表示されるはずです。
      cd ./nextjs-emotion-layout-sample
      
      # 開発サーバ起動
      # localhost:3000
      npm run dev
      
      20231231_next_app_design_02
      ▲サンプル画面が表示される

      エレキベア
      エレキベア
      簡単クマ〜〜〜〜

      ESLint、Prettierの設定

      マイケル
      マイケル
      次にESLint、Prettierを導入します。 React、Next.js用も含めて、下記コマンドでインストールします。
      // ESLint, Prettier関連
      npm install --save-dev \
      prettier \
      eslint \
      eslint-config-next \
      eslint-config-prettier \
      eslint-plugin-import \
      eslint-plugin-react \
      eslint-plugin-react-hooks
      
      // TypeScript用
      npm install --save-dev \
      typescript-eslint \
      @typescript-eslint/eslint-plugin \
      @typescript-eslint/parser
      
      エレキベア
      エレキベア
      たくさんあるクマ・・・
      マイケル
      マイケル
      そして設定ファイルとして .eslintrc.json、.prettierrc.json を作成します。 今回は下記のように設定しました。
      touch .eslintrc.json
      touch .prettierrc.json
      
      {
        // eslint設定を読み込む
        // recommended: 推奨設定
        "extends": [
          "next",
          "next/core-web-vitals",
          "eslint:recommended",
          "plugin:react/recommended",
          "plugin:react-hooks/recommended",
          "plugin:@typescript-eslint/recommended",
          "plugin:import/recommended",
          "plugin:import/typescript"
        ],
        "rules": {
          // React17からreactインポートが不要になったためoffにする
          "react/react-in-jsx-scope": "off",
          // importをアルファベット順に並べる
          "import/order": [2, { "alphabetize": { "order": "asc" } }]
        }
      }
      
      
      {
          "trailingComma": "all",
          "endOfLine": "lf",
          "semi": false,
          "singleQuote": true,
          "printWidth": 80,
          "tabWidth": 2
        }
      
      
      エレキベア
      エレキベア
      この辺はお好みに合わせて変更するクマね
      マイケル
      マイケル
      最後にpackage.jsonを下記のように設定します。 これによりnpm run check、npm run formatでコードのチェック・整形を行うことができます。
      {
      
      ・・・略・・・
      
        "version": "0.1.0",
        "private": true,
        "scripts": {
      
      ・・・略・・・
      
          "lint:check": "next lint --dir src",
          "lint:fix": "next lint --fix --dir src",
          "prettier:check": "prettier --check src",
          "prettier:write": "prettier --write src",
          "check": "npm run lint:check && npm run prettier:check",
          "format": "npm run lint:fix && npm run prettier:write"
        },
      
      ・・・略・・・
      
      }
      
      
      # コードのチェックを行う
      npm run check
      
      # コードの整形を行う
      npm run format
      
      エレキベア
      エレキベア
      これでコード整形は完璧クマ〜〜〜

      スタイリングの初期設定

      マイケル
      マイケル
      次にEmotionを導入していきます。 こちらは設定が少々手間がかかる印象でした。
      Emotionのインストール
      マイケル
      マイケル
      今回はCSSPorpを使用するため、@emotion/reactのみインストールします。
      # Emotion のインストール
      # CSSPropを使用するため @emotion/styled は使用しない
      npm install @emotion/react
      
      マイケル
      マイケル
      インストールしたらnext.config.js、tsconfig.jsのそれぞれの設定ファイルにEmotionを有効にするよう設定します。
      /** @type {import('next').NextConfig} */
      const nextConfig = {
        reactStrictMode: true,
        compiler: (() => {
          let compilerConfig = {
            // Emotion有効化
            emotion: true,
          };
          return compilerConfig;
        })(),
      };
      
      module.exports = nextConfig;
      
      {
        "compilerOptions": {
      
      ・・・略・・・
      
          "jsxImportSource": "@emotion/react"
        },
        "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
        "exclude": ["node_modules"]
      }
      
      マイケル
      マイケル
      あとはESLintの設定でエラーが出ないよう設定すれば完了です!
      {
        "extends": [
      
      ・・・略・・・
      
        ],
        "rules": {
      
      ・・・略・・・
      
          // no used, no-named-as-default, no-unresolvedはうざいのでoff
          "@typescript-eslint/no-unused-vars": "off",
          "import/no-named-as-default": "off",
          "import/no-unresolved": "off",
          // Emotionでエラーが出るため
          "react/no-unknown-property": ["error", { "ignore": ["css"] }]
        }
      }
      
      エレキベア
      エレキベア
      中々面倒くさいクマ・・・
      コンポーネントへのスタイル適用
      マイケル
      マイケル
      基本的な使い方としては、下記のようにコンポーネント内でCSSを定義・適用します。
      import { css } from '@emotion/react'
      
      const styleCard = css`
        background: black;
        font-size: 40px;
        color: white;
        width: 400px;
        height: 80px;
        margin: 80px;
        border-radius: 20px;
        display: flex;
        justify-content: center;
        align-items: center;
      `
      
      const Card = (props: {
        text: string
      }) => {
        return <div css={styleCard}>{props.text}</div>
      }
      export default Card
      
      
      20231231_next_app_design_03
      エレキベア
      エレキベア
      分かりやすいクマ〜〜〜
      グローバルスタイルの適用
      マイケル
      マイケル
      グローバルにCSSを使用したい場合には、Globalタグにスタイルを設定して囲むことで適用することができます。
      import { Global, css } from '@emotion/react'
      import type { AppProps } from 'next/app'
      
      // グローバルのスタイル
      const styleGlobal = css`
        html,
        body,
        textarea {
          margin: 0;
          padding: 0;
          font-family: 'Helvetica Neue', Arial, 'Hiragino Kaku Gothic ProN',
            'Hiragino Sans', 'BIZ UDPGothic', Meiryo, sans-serif;
        }
      `
      
      export default function App({ Component, pageProps }: AppProps) {
        return (
          <>
            <Global styles={styleGlobal} />
            <Component {...pageProps} />
          </>
        )
      }
      
      
      エレキベア
      エレキベア
      従来のCSSのように使えるのクマね
      テーマの適用
      マイケル
      マイケル
      グローバルCSS以外にも、共通のテーマを持たせる方法もあります。 例えば下記のようなカラーのテーマを用意してみます。
      // エレキベアテーマ
      export const elekibear = {
        colors: {
          primaryYellow: '#fff500',
          primaryDarkGray: '#222222',
          primaryLightGray: '#3d3b39',
          primaryWhite: '#f2f2f2',
          primaryBlue: '#14639C',
        },
      };
      
      import '@emotion/react';
      
      declare module '@emotion/react' {
        interface Theme {
          colors: Colors;
        }
      }
      
      interface Colors {
        primaryYellow: string;
        primaryDarkGray: string;
        primaryLightGray: string;
        primaryWhite: string;
        primaryBlue: string;
      }
      
      マイケル
      マイケル
      これをThemeProviderタグに設定して囲むことで適用することができます。 使い方はCSS指定の際にthemeを引数として受け取るようにするだけです。
      import { Global, ThemeProvider, css } from '@emotion/react'
      import type { AppProps } from 'next/app'
      import { elekibear } from '@/theme/theme'
      
      ・・・略・・・
      
      export default function App({ Component, pageProps }: AppProps) {
        return (
          <ThemeProvider theme={elekibear}>
            <Global styles={styleGlobal} />
            <Component {...pageProps} />
          </ThemeProvider>
        )
      }
      
      
      import { Theme, css } from '@emotion/react'
      
      const styleCard = (theme: Theme) => css`
        background: ${theme.colors.primaryDarkGray};
        font-size: 40px;
        color: ${theme.colors.primaryYellow};
        width: 400px;
        height: 80px;
        margin: 80px;
        border-radius: 20px;
        display: flex;
        justify-content: center;
        align-items: center;
      `
      
      const Card = (props: { text: string }) => {
        return <div css={styleCard}>{props.text}</div>
      }
      export default Card
      
      
      20231231_next_app_design_04
      ▲テーマに設定したカラーが表示される

      Storybookの導入

      マイケル
      マイケル
      最後にStorybookを導入します。 下記コマンドを実行すると、ライブラリとサンプルコードが格納されます。
      npx sb@latest init
      
      {
      
      ・・・略・・・
      
        "scripts": {
      
      ・・・略・・・
      
          "storybook": "storybook dev -p 6006",
          "build-storybook": "storybook build"
        },
      
      ・・・略・・・
      
      }
      
      
      ▲コマンドも追加される
      マイケル
      マイケル
      インストール後に npm run storybook を実行すると、localhost:6006 でStorybookの画面にアクセスできるようになっているはずです。
      20231231_next_app_design_05
      ▲Storybookの画面が開く

      エレキベア
      エレキベア
      この画面で作成したコンポーネントを確認できるようになるクマね
      マイケル
      マイケル
      Storybookには、XXX.stories.tsx の拡張子でファイルを作成することで追加できます。 下記のようにStoryFn型で定義したコンポーネントをbindすることで表示します。
      import { Meta, StoryFn } from '@storybook/react'
      import Card from '.'
      
      export default {
        title: 'Home/PostCard',
      } as Meta<typeof Card>
      
      const Template: StoryFn<typeof Card> = (args) => <Card {...args} />
      export const SampleCard = Template.bind({})
      SampleCard.args = {
        text: 'sample',
      }
      
      
      マイケル
      マイケル
      通常はこれだけで表示されるようになるのですが、Emotionを使用している場合にはスタイル適用の追加対応が必要になります。 下記二点について追加で対応します。
      • preview.ts の修正
        • preview.ts -> preview.tsx にリネーム
        • グローバルスタイル、テーマの適用を追加する
      import React from 'react';
      import type { Decorator, Preview } from '@storybook/react';
      import { Global, ThemeProvider, css } from '@emotion/react';
      import { elekibear } from '../src/theme/theme';
      
      const preview: Preview = {
        parameters: {
          actions: { argTypesRegex: '^on[A-Z].*' },
          controls: {
            matchers: {
              color: /(background|color)$/i,
              date: /Date$/i,
            },
          },
        },
      }
      
      // グローバルのスタイル
      const styleGlobal = css`
        html,
        body,
        textarea {
          margin: 0;
          padding: 0;
          font-family: 'Helvetica Neue', Arial, 'Hiragino Kaku Gothic ProN',
            'Hiragino Sans', 'BIZ UDPGothic', Meiryo, sans-serif;
        }
      `;
      
      export const decorators = [
        (Story) => (
          <ThemeProvider theme={elekibear}>
            <Global styles={styleGlobal} />
            <Story />
          </ThemeProvider>
        ),
      ];
      
      export default preview
      
      
      ▲グローバルスタイル、テーマの適用
      npm install --save-dev @emotion/babel-preset-css-prop
      
      import type { StorybookConfig } from '@storybook/nextjs'
      
      const config: StorybookConfig = {
      
      ・・・略・・・
      
        // Emotionを適用するため追加
        babel: async (options) => {
          options.presets?.push('@emotion/babel-preset- css-prop');
          return options;
        },
      }
      export default config
      
      
      ▲EmotionのBabel設定を追加
      マイケル
      マイケル
      以上の設定でEmotionのスタイルが適用されているはずです!
      20231231_next_app_design_06
      エレキベア
      エレキベア
      やったクマ〜〜〜〜

      レイアウト実装

      マイケル
      マイケル
      環境が整ったところで、試しに簡単なレイアウトを作成してみます。 当サイトでもベースとなっている、ヘッダー、タイトルエリア、メインエリア、サイドバー、フッターの五つのエリアで構成されている画面になります。
      20231231_next_app_design_07
      全体のレイアウト
      マイケル
      マイケル
      全体のレイアウトの指定について、 src/pages/_app.tsx src/pages/index.tsx はそれぞれ下記のようにしています。
      import { Global, ThemeProvider, css, Theme } from '@emotion/react'
      import type { AppProps } from 'next/app'
      import Head from 'next/head'
      import { elekibear } from '@/theme/theme'
      
      // グローバルのスタイル
      const styleGlobal = (theme: Theme) => css`
        html,
        body,
        textarea {
          margin: 0;
          padding: 0;
          font-family: 'Helvetica Neue', Arial, 'Hiragino Kaku Gothic ProN',
            'Hiragino Sans', 'BIZ UDPGothic', Meiryo, sans-serif;
        }
        body {
          /** チェック柄 */
          background: linear-gradient(
              45deg,
              #2a2a2a 25%,
              transparent 25%,
              transparent 75%,
              #2a2a2a 75%
            ),
            linear-gradient(
              45deg,
              #2a2a2a 25%,
              transparent 25%,
              transparent 75%,
              #2a2a2a 75%
            );
          background-color: ${theme.colors.primaryDarkGray};
          background-size: 60px 60px;
          background-position:
            0 0,
            30px 30px;
        }
      `
      
      export default function App({ Component, pageProps }: AppProps) {
        return (
          <>
            <Head>
              <title>Next.js × Emotion サンプル</title>
              <meta name="description" content="Generated by create next app" />
              <meta name="viewport" content="width=device-width, initial-scale=1" />
              <link rel="icon" href="/favicon.ico" />
            </Head>
            <ThemeProvider theme={elekibear}>
              <Global styles={styleGlobal} />
              <Component {...pageProps} />
            </ThemeProvider>
          </>
        )
      }
      
      
      ▲Headタグも含めて_app.tsxに定義する
      import CenterText from '@/components/CenterText'
      import Layout from '@/templates/Layout'
      
      /**
       * ホームページ
       * @returns
       */
      export default function Home() {
        return (
          <>
            <Layout
              mainContent={<CenterText text="メイン" />}
              sideBarContent={<CenterText text="サイドバー" />}
            />
          </>
        )
      }
      
      
      ▲ページからはLayoutコンポーネントを表示するようにする
      エレキベア
      エレキベア
      ページ側ではLayoutコンポーネントを表示しているだけクマね
      マイケル
      マイケル
      Layoutコンポーネントに渡しているCenterTextコンポーネントについては、 下記のように中央にテキスト表示だけのコンポーネントになっています。 このようにUIパーツごとに分けて開発できるのがReactの強みですね!
      import { css } from '@emotion/react'
      
      const styleRoot = css`
        display: flex;
        justify-content: center;
        align-items: center;
        width: 100%;
        height: 100%;
      `
      
      const styleText = (color?: string) => css`
        font-size: 20px;
        font-weight: bold;
        padding: 12px 0px;
        color: ${color ? color : 'black'};
      `
      
      /**
       * 要素中央にテキストを表示する
       * @param props
       * @returns
       */
      const CenterText = (props: { text: string; color?: string }) => {
        return (
          <div css={styleRoot}>
            <div css={styleText(props.color)}>{props.text}</div>
          </div>
        )
      }
      export default CenterText
      
      
      ▲中央にテキストを表示するだけのコンポーネント
      マイケル
      マイケル
      Layoutコンポーネント内では、ヘッダーやフッターといったコンポーネントをそれぞれ表示するようにしています。 xl、lgといった指定は、メディアクエリの共通ブレイクポイントとしてEmotionで指定できるようにしました。
      import { css, Theme } from '@emotion/react'
      import Footer from '../Footer'
      import Header from '../Header'
      import Sidebar from '../Sidebar'
      import TitleArea from '../TitleArea'
      import { lg, xl } from '@/style/media'
      
      const styleContentWrapper = css`
        display: flex;
        justify-content: space-between;
        flex-wrap: wrap;
        margin: 0px auto;
        width: 1280px;
      
        ${xl(css`
          width: auto;
        `)}
        ${lg(css`
          display: block;
        `)}
      `
      
      const styleMain = (theme: Theme) => css`
        background-color: ${theme.colors.primaryWhite};
        position: relative;
        margin: 0 0.5%;
        width: 800px;
      
        ${xl(css`
          width: 65%;
        `)}
        ${lg(css`
          width: auto;
          margin: 0px 10px;
        `)}
      `
      
      interface LayoutProps {
        mainContent: React.ReactNode
        sideBarContent: React.ReactNode
      }
      
      /**
       * ページ共通レイアウト
       * @param props
       * @returns
       */
      const Layout = (props: LayoutProps) => {
        return (
          <>
            <Header />
            <TitleArea />
            <div css={styleContentWrapper}>
              <main css={styleMain}>{props.mainContent}</main>
              <Sidebar sideBarContent={props.sideBarContent}></Sidebar>
            </div>
            <Footer />
          </>
        )
      }
      export default Layout
      
      
      ▲Layoutコンポーネント
      import { SerializedStyles, css } from '@emotion/react'
      
      /** レスポンシブブレークポイント */
      
      export const xl = (childen: SerializedStyles) => {
        return css`
          @media (max-width: 1280px) {
            ${css(childen)}
          }
        `
      }
      
      export const lg = (childen: SerializedStyles) => {
        return css`
          @media (max-width: 1024px) {
            ${css(childen)}
          }
        `
      }
      
      export const md = (childen: SerializedStyles) => {
        return css`
          @media (max-width: 768px) {
            ${css(childen)}
          }
        `
      }
      
      export const sm = (childen: SerializedStyles) => {
        return css`
          @media (max-width: 640px) {
            ${css(childen)}
          }
        `
      }
      
      export const xlWidth = 1280
      export const lgWidth = 1024
      export const mdWidth = 768
      export const smWidth = 640
      
      
      ▲メディアクエリのブレイクポイント対応
      エレキベア
      エレキベア
      ブレイクポイントを指定できるようにしておけば レスポンシブ対応しやすそうクマね
      マイケル
      マイケル
      ヘッダーやサイドバー等についてはそれぞれ以下のようになっています。 実装内容について特に特殊なことはしていないので、解説は省略します。
      ヘッダー、フッター
      import { Theme, css } from '@emotion/react'
      import CenterText from '@/components/CenterText'
      
      const styleRoot = (theme: Theme) => css`
        width: 100%;
        background-color: ${theme.colors.primaryDarkGray};
      `
      
      const styleContent = css`
        height: 48px;
        width: 100%;
        display: flex;
        align-items: center;
        justify-content: space-between;
      `
      
      /**
       * ヘッダー
       * @returns
       */
      const Header = () => {
        return (
          <>
            <header>
              <div css={styleRoot}>
                <div css={styleContent}>
                  <CenterText text="ヘッダー" color="white" />
                </div>
              </div>
            </header>
          </>
        )
      }
      export default Header
      
      
      import { Theme, css } from '@emotion/react'
      import CenterText from '@/components/CenterText'
      
      const styleRoot = (theme: Theme) => css`
        width: 100%;
        height: 48px;
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: ${theme.colors.primaryDarkGray};
        margin-top: 20px;
      `
      
      /**
       * フッター
       * @returns
       */
      const Footer = () => {
        return (
          <footer css={styleRoot}>
            <CenterText text="フッター" color="white" />
          </footer>
        )
      }
      export default Footer
      
      
      タイトルエリア
      import { Theme, css } from '@emotion/react'
      import CenterText from '@/components/CenterText'
      
      const styleTitleArea = (theme: Theme) => css`
        height: 280px;
        background-color: ${theme.colors.primaryYellow};
        display: flex;
        justify-content: center;
        margin-bottom: 20px;
        position: relative;
      `
      
      const TitleArea = () => {
        return (
          <div css={styleTitleArea}>
            <CenterText text="タイトル" />
          </div>
        )
      }
      export default TitleArea
      
      
      サイドバー
      import { Theme, css } from '@emotion/react'
      import { lg, xl } from '@/style/media'
      
      const styleRoot = (theme: Theme) => css`
        background-color: ${theme.colors.primaryWhite};
        padding: 0px;
        margin: 0px auto;
        width: 400px;
      
        ${xl(css`
          width: 30%;
        `)}
        ${lg(css`
          display: block;
          width: auto;
          margin: 10px;
        `)};
      `
      
      /**
       * サイドバー
       * @param props
       * @returns
       */
      const Sidebar = (props: { sideBarContent: React.ReactNode }) => {
        return <div css={styleRoot}>{props.sideBarContent}</div>
      }
      export default Sidebar
      
      
      マイケル
      マイケル
      以上で2カラムのレイアウトが実装できているはずです。 このようにUIコンポーネント単位で分けながら開発を進めます。
      エレキベア
      エレキベア
      部品ごとに分けるから管理や再利用がしやすそうクマね

      Netlifyサーバへのデプロイ

      マイケル
      マイケル
      最後に、今回作ったプロジェクトをNetlifyにデプロイしてみます。 アカウント登録後、「Add new site」ボタンからプロジェクトを追加できます。 今回はGitHubリポジトリをそのままデプロイするため「Import an existing project」を選択します。

      Netlify 公式ページ

      20231231_next_app_design_12
      マイケル
      マイケル
      デプロイするサービスとしてGitHub、デプロイしたいリポジトリを選択します。 設定画面にてデプロイボタンを押下すればリポジトリと紐づけられ、以降は対象のブランチに変更がある度にデプロイされます。
      20231231_next_app_design_13
      ▲デプロイするリポジトリを選択

      20231231_next_app_design_14
      ▲設定画面 - Build command に設定したコマンドがビルド時に実行される

      エレキベア
      エレキベア
      簡単クマ〜〜〜〜
      マイケル
      マイケル
      デプロイが完了次第、割り振られたURLを開くとブラウザ上で確認できるようになっているはずです。
      20231231_next_app_design_15
      ▲デプロイ完了後、URLを開くとデプロイされていることが確認できる

      20231231_next_app_design_16
      ▲正常に表示することができた

      エレキベア
      エレキベア
      これでプロジェクトのテンプレートからデプロイ環境まで一通り整ったクマね
      マイケル
      マイケル
      Netlifyでは、今回紹介したGitHubリポジトリのデプロイ以外でも ドラッグ&ドロップ、コマンドによるデプロイ方法もサポートしています。 この辺りは好みや用途に合わせて選択してください。
      # NetlifyCLIのインストール
      npm install -g netlify-cli
      netlify -v
      
      # ログイン
      netlify login
      
      # プロジェクトとの紐付け
      # Site Name or Site ID で紐づける
      netlify link
      
      # デプロイ方法
      # フォルダ名(-d)は uploads を指定
      # そのフォルダ内の構成と完全に同期される
      netlify deploy --prod -d uploads
      
      
      コマンドによるデプロイ方法
      エレキベア
      エレキベア
      GitHubで管理しない資産はコマンドを使用する方が良さそうクマね

      おわりに

      マイケル
      マイケル
      というわけで今回はNext.jsプロジェクトの設計について触れていきました! どうだったかな??
      エレキベア
      エレキベア
      ライブラリの組み合わせ、環境構築は中々面倒だったクマ でも一度整えてしまえば快適な開発環境になるクマね
      マイケル
      マイケル
      最初は面倒くさいけど、やっぱりコーディングできる状態まで来ると楽しいね! あとはWordPressのデータをどう移行するかすごく悩んだけど、とりあえずは今回の構成でどうにかなりそうです。
      マイケル
      マイケル
      次回は実際にWordPressデータを移行・表示する流れを紹介していきます! アデューー!!
      エレキベア
      エレキベア
      クマ〜〜〜〜

      【Next.js】第一回 WordPressブログをNext.jsに移行する 〜全体設計、環境構築編〜 〜完〜

      【都会のエレキベア】ブログを大幅リニューアル!WordPressからNext.jsに移行するまでの流れをまとめる
      2024-01-01
      【Next.js】第二回 WordPressブログをNext.jsに移行する 〜WordPressデータの移行・表示編〜
      2023-12-31
      【Next.js】第三回 WordPressブログをNext.jsに移行する 〜Markdown執筆環境構築編〜
      2023-12-31
      【Next.js】第四回 WordPressブログをNext.jsに移行する 〜サーバ移行・SEO・広告設定編〜
      2023-12-31

      Next.jsReactJavaScriptWordPress関連フロントエンド関連SSGStorybookEmotionNode.jsp5.js
      2023-12-31

      関連記事
      【ゲーム数学】第九回 p5.jsで学ぶゲーム数学「フーリエ解析」
      2024-05-12
      【Node.js】廃止されたAmazonアソシエイト画像リンクをAmazon Product Advertising API経由で復活させる
      2024-01-08
      【都会のエレキベア】ブログを大幅リニューアル!WordPressからNext.jsに移行するまでの流れをまとめる
      2024-01-01
      【Next.js】第四回 WordPressブログをNext.jsに移行する 〜サーバ移行・SEO・広告設定編〜
      2023-12-31
      【Next.js】第三回 WordPressブログをNext.jsに移行する 〜Markdown執筆環境構築編〜
      2023-12-31
      【Next.js】第二回 WordPressブログをNext.jsに移行する 〜WordPressデータの移行・表示編〜
      2023-12-31
      【Electron × Vue3】カテゴリ情報のCSVデータを操作するツールを作る
      2023-12-31
      【Electron × Vue3】画像をリサイズして任意の場所に保存するツールを作る
      2023-12-31