⛩️

Next.js + HeadlessWordPress + 少しだけNest.js で友人のwebサイトを作り直してみた。

2022/12/03に公開

はじめに

タイトルの通りですが友人のお寺webサイトを作り直してみました。
もともと私が1年程前にWordPressを使用して作ったwebサイトでした。

今回、作ったwebサイト

https://ganrakuji.com

リポジトリ

https://github.com/kento26/ganrakuji-web-next
https://github.com/kento26/ganrakuji-web-nest

使用技術、ツール

  • デザイン

    • Figma
  • CMS

    • WordPress
  • フロント

    • 言語 TypeScript
    • フレームワーク Next.js
    • その他 GraphQL
  • API

    • 言語 TypeScript
    • フレームワーク Nest.js
  • ホスティング

    • Vercel (Next.js)
    • render (Nest.js)

構成

Jamstack構成となっています。
WordPressで記事の管理を行い、Next.jsでコンテンツを生成しています。

課題

1.画像のURLからWordPressのURLが見えてしまう。

解決方法としては、画像用のサーバーを用意しました。
保存した画像は画像用サーバーにアップロードされる様にWordPressの設定を行いました。
具体的には、option.phpからupload_pathとupload_url_pathの設定を行いました。

https://halkyo.com/post/others/jamstack-blog-on-next-js-and-wordpress
https://lpeg.info/wordpress/upload_url_path.html

2.お問い合わせフォームの送信先からWordPressのURLが見えてしまう。

お問い合わせはWordPressのプラグインContact Form 7のAPIを使用しています。
そのままContact Form 7のAPIを使用してしまうとWordPressのURLが見えてしまうので
Nest.jsで作ったAPIを経由してデータを送信する事にしました。

デザイン

まずは、デザインから始まりました。
落ち着いた雰囲気のあるwebサイトになるようデザインをしました。(したつもり)

具体的には、
1.背景色は白ではなくベージュ色にしました。
感覚的な事ですが真っ白より淡いベージュ色の方が落ち着いた雰囲気がでると思いました。
あと目に優しい?

2.フォントの色は黒(#000)ではなく#333にしました。
真っ黒(#000)だと重い印象になるので少し薄い#333にしました。
デザイナーさんが作ったデザインを見ていると真っ黒(#000)ではなく#333など少し薄い色をフォントに使用している事があったので真似てみました。

3.日本語フォントはSawarabi Minchoを使用しました。
明朝体で和の雰囲気がでれば良いなと思い選びました。

PCのデザインしかないですが、下記の様になりました。
とりあえずこのデザインでコーディングを進める事にしました。

PCデザイン

https://baigie.me/officialblog/2020/07/21/web-color-planning/
https://gendaidesign.com/category/temple/

CMS

既存のWordPressを使い回すことにしました。
Headless化にともない追加したプラグインは、下記の3つです。

  • WP GraphQL
  • WPGraphQL for Advanced Custom Fields
  • Vercel Deploy Hooks

https://ja.wordpress.org/plugins/wp-graphql/
https://www.wpgraphql.com/acf
https://ja.wordpress.org/plugins/vercel-deploy-hooks/

WP GraphQL、WPGraphQL for Advanced Custom Fields

WP GraphQLとWPGraphQL for Advanced Custom FieldsはWordPressでGraphQLを使用できるようになるプラグインです。
データはWordPress REST APIを使用して取得する事も可能だと思いますが、GraphQLを勉強したかったのでこのようにしました。

プラグインを追加すると下記の様にクエリを作成できます。

Vercel Deploy Hooks

Vercel Deploy HooksはWordPressからVercelのDeploy HooksにPOSTリクエストをなげてビルドをしてもらう為のプラグインになります。

使い方としては、Vercel Deploy Hook URLにVercelで生成したDeploy Hookを設定。

Build Siteを押すとビルドされました。

プラグインの中を少し見てみましたが、ajaxを使用してPOSTリクエストを投げているみたいでした。

Vercel Deploy Hooksのコード
function vercelDeploy() {
  return $.ajax({
    type: 'POST',
    url: webhook_url,
    dataType: 'json',
  });
}

$('#build_button').on('click', function (e) {
  e.preventDefault();

  vercelDeploy()
    .done(function (res) {
      console.log('success');
      $('#build_status').html('Building in progress');
      $('#build_status_id').removeAttr('style');
      $('#build_status_id').html('<b>ID</b>: ' + res.job.id);
      $('#build_status_state').removeAttr('style');
      $('#build_status_state').html('<b>State</b>: ' + res.job.state);
      $('#build_status_createdAt').removeAttr('style');
      $('#build_status_createdAt').html(
        '<b>Created At</b>: ' + new Date(res.job.createdAt).toLocaleString(),
      );
    })
    .fail(function () {
      console.error('error res => ', this);
      $('#build_status').html(
        'There seems to be an error with the build',
        this,
      );
    });
});

https://vercel.com/docs/concepts/git/deploy-hooks

フロントエンド

ディレクトリ構成

ディレクトリ構成
├── components
│   ├── atoms
│   ├── molecules
│   ├── organisms
│   ├── providers
│   └── templates
├── graphql
│   ├── config
│   ├── documents
│   └── generated
├── hooks
├── lib
├── pages
├── public
├── router
├── schema
└── styles
    ├── components
    │   ├── atoms
    │   ├── molecules
    │   ├── organisms
    │   └── providers
    ├── config
    │   ├── mixins.scss
    │   ├── reset.scss
    │   └── variables.scss
    └── globals.scss

コンポーネントのディレクトリ構成に関してはAtomic Designを採用しました。
これまでAtomic Designという手法に触れてこなかったので勉強の為に選択しました。

moleculesとorganismsの分け方

organismsとmoleculesの分け方で迷う事がありました。
他の記事を参考に下記の事を意識して分けました。

molecules
他のコンポーネントを補助する役割。
汎用性がある。
inputフォームなど

organisms
単体で存在できる。
特定のコンテンツ、機能をもっている。
お問い合わせフォームなど

https://buildersbox.corp-sansan.com/entry/2022/01/06/110000
https://note.com/tabelog_frontend/n/n4b8bcb44294c

providersディレクトリ

こちらはAtomic Designにはない新規に追加したディレクトリになります。
役割としては、アニメーションなどコンポーネントをまとめています。

こちらの記事のfunctionalディレクトリに該当します。
https://zenn.dev/yoshiko/articles/99f8047555f700

GraphQL

WordPressからデータを取得する為にgraphql-requestを使用しています。
リクエストを投げてデータを取得する。それ以上の機能は必要なかったのでgraphql-requestを選択しました。
https://github.com/prisma-labs/graphql-request

GraphQLのスキーマからTypeScriptの型を生成するためにGraphQL Code Generatorを使用しました。

設定ファイルなど

設定ファイル

codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  overwrite: true,
  schema: 'WordPressで設定したgraphqlのエンドポイントを設定する',
  // documents クエリのファイルを指定する。
  documents: 'graphql/documents/*.graphql',
  // generates 出力先のファイルと設定
  generates: {
    'graphql/generated/client.ts': {
      config: {
        skipTypename: true,
        avoidOptionals: true,
        maybeValue: 'T',
      },
      plugins: [
        'typescript',
        'typescript-operations',
        'typescript-graphql-request',
      ],
    },
  },
};

export default config;

WordPressのGraphiQL IDEを使用して作成したクエリをコピペします。

graphql/documents/fetchHomePageQuery.graphql
query fetchHomePageQuery {
  pages {
    edges {
      node {
        commonACF {
          facilitiesAndEquipment {
            facilitiesAndEquipmentText
            facilitiesAndEquipmentTitle
            facilitiesAndEquipmentImage {
              sourceUrl(size: LARGE)
            }
          }
          historyText
          historyImage01 {
            sourceUrl(size: LARGE)
          }
          historyImage02 {
            sourceUrl(size: LARGE)
          }
          introductionName
          introductionText
          mainvisualSliderImage {
            sliderImage {
              sourceUrl(size: LARGE)
            }
          }
          tombText
          yogaImage {
            sourceUrl(size: LARGE)
          }
          yogaText
        }
      }
    }
  }
}

package.jsonにコマンド追加

package.json
"codegen": "graphql-codegen --config codegen.ts"

codegenを実行すると型などが記述されたファイルが生成されます。
生成されたファイル(一部)

graphql/generated/client.ts
export type FetchHomePageQueryQueryVariables = Exact<{ [key: string]: never }>;

export type FetchHomePageQueryQuery = {
  pages: {
    edges: Array<{
      node: {
        commonACF: {
          historyText: string;
          introductionName: string;
          introductionText: string;
          tombText: string;
          yogaText: string;
          facilitiesAndEquipment: Array<{
            facilitiesAndEquipmentText: string;
            facilitiesAndEquipmentTitle: string;
            facilitiesAndEquipmentImage: { sourceUrl: string };
          }>;
          historyImage01: { sourceUrl: string };
          historyImage02: { sourceUrl: string };
          mainvisualSliderImage: Array<{ sliderImage: { sourceUrl: string } }>;
          yogaImage: { sourceUrl: string };
        };
      };
    }>;
  };
};

export const FetchHomePageQueryDocument = gql`
  query fetchHomePageQuery {
    pages {
      edges {
        node {
          commonACF {
            facilitiesAndEquipment {
              facilitiesAndEquipmentText
              facilitiesAndEquipmentTitle
              facilitiesAndEquipmentImage {
                sourceUrl(size: LARGE)
              }
            }
            historyText
            historyImage01 {
              sourceUrl(size: LARGE)
            }
            historyImage02 {
              sourceUrl(size: LARGE)
            }
            introductionName
            introductionText
            mainvisualSliderImage {
              sliderImage {
                sourceUrl(size: LARGE)
              }
            }
            tombText
            yogaImage {
              sourceUrl(size: LARGE)
            }
            yogaText
          }
        }
      }
    }
  }
`;

export type SdkFunctionWrapper = <T>(
  action: (requestHeaders?: Record<string, string>) => Promise<T>,
  operationName: string,
  operationType?: string,
) => Promise<T>;

const defaultWrapper: SdkFunctionWrapper = (
  action,
  _operationName,
  _operationType,
) => action();

export function getSdk(
  client: GraphQLClient,
  withWrapper: SdkFunctionWrapper = defaultWrapper,
) {
  return {
    fetchHomePageQuery(
      variables?: FetchHomePageQueryQueryVariables,
      requestHeaders?: Dom.RequestInit['headers'],
    ): Promise<FetchHomePageQueryQuery> {
      return withWrapper(
        (wrappedRequestHeaders) =>
          client.request<FetchHomePageQueryQuery>(
            FetchHomePageQueryDocument,
            variables,
            { ...requestHeaders, ...wrappedRequestHeaders },
          ),
        'fetchHomePageQuery',
        'query',
      );
    },
  };
}
export type Sdk = ReturnType<typeof getSdk>;

https://developers.wpengine.com/blog/graphql-code-generator-for-wpgraphql
https://techlife.cookpad.com/entry/2021/03/24/123214

API

はじめは、Next.jsのAPI RoutesでAPIを作ろうと考えていたのですがNext.jsのSGではAPI Routesを使用できないみたいなのでNest.jsで作る事にしました。

Nest.jsを選んだ理由としては
TypeScriptで開発できる。
業務でNode.jsを使用しているので勉強の為。

https://nextjs.org/docs/advanced-features/static-html-export
https://nestjs.com/

ディレクトリ構成

modulesというディレクトリを追加してその中に各モジュールを入れる構成にしています。

ディレクトリ構成
src
 ├── app.module.ts
 ├── main.ts
 └── modules
     └── contact
         ├── contact.controller.ts
         ├── contact.dto.ts
         ├── contact.module.ts
         ├── contact.service.ts
         └── validation.pipe.ts

ホスティングサービス

ホスティングサービスはrenderを選択しました。
はじめは、Herokuを使用しようと考えていたのですがは無償プランが廃止されるという事なのでrenderにしました。
https://render.com/

今後の課題

  • renderがスリープするのでお問い合わせに時間がかかる。
  • パフォーマンスの改善。画像の最適化、遅延読み込み、バンドルサイズの削減。
  • お問い合わせに関して今回は既存のWordPressを使いまわしたのでContact Form 7を使用したがSendGridなどのサービスを利用した方が良いか検討する。

などなど。

Discussion