microCMS

Next.jsとAuth0で会員制メディアを作る【3. 完成編】

柴田 和祈

この記事は公開後、1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、柴田です。

「会員制メディア」のチュートリアルを全3回に分けてお届けしております。
今回は第3回の完成編です。

===

  1. 認証編
  2. ページ作成編
  3. 完成編

===

前回は一覧ページ、公開記事・会員向け記事ページを作り、Vercelにデプロイするところまでを行いました。
今回は一旦完成と呼べるところまで、こちらのクオリティUPを進めていきたいと思います。

1. 管理画面からの記事公開の反映

前回の状態では、microCMSで書いた記事を本番に反映するためには毎回Vercelのビルド・デプロイを走らせる必要があります。
手動でそれを行うのは現実的ではないので、microCMSとVercelの連携方法を考えます。

3つの方法が考えられるので、ひとつずつ解説していきます。

Webhookを用いる

一つ目は、microCMSからWebhook経由でビルド・デプロイをさせる方法です。

この方法では、記事を更新するごとに全ページをビルドする必要があります。
そのため、記事数が多くなってくるとデプロイされるまで結構時間がかかってしまいます。

ページ単位のコンテンツ量にも寄りますが、デプロイまで1時間以上かかってしまうようなケースもあります。

Next.jsのISR(Incremental Static Regeneration)機能を用いる

次は、Next.jsのISRを用いる方法です。
ISRはrevalidateというページの保持期間を指定し、保持期間内であればそのキャッシュを返し、過ぎていたらサーバーに最新データがないか問い合わせに行く形式です。

ISRを用いる場合、ページ全体のビルドを行う必要がないため、デプロイに時間がかかるという問題はクリアされます。
一方で、最大でもrevalidateに指定した時間分、反映まで待つ必要があるというデメリットがあります。

Next.jsのOn-demand ISR機能を用いる

最後に、Next.jsのOn-demand ISRを用いる方法です。
On-demand ISRについては下記記事にて詳しく解説しております。

Next.jsのOn-demand ISRで、ビルド不要の高速配信を実現する

こちらの方法は、言うなればWebhookとISRの合わせ技です。
microCMSから記事を公開したタイミングでWebhookが飛び、関連するページのみを再生成する形となります。

「関連する」ページはサイト仕様によるので、そこは自前でロジックを実装する必要があります。

例えば、記事に紐づける「タグ名」を変更した場合、そのタグが付いている全記事とタグ一覧ページを再生成するといった処理が必要です。

必要箇所のみを即時に更新できる反面、実装が複雑になりやすいデメリットもあります。

=====

方法はこれら3つがありますが、今回は2つ目のISRを採用したいと思います。
実装が簡単で、かつrevalidate時間を短くすればそこまで運用に支障はないと考えられるためです。

今回の構成では一覧ページと公開記事ページがSG、会員向け記事ページがSSRとなっています。
SSRの方は特にビルドは不要で即時反映されるので、ISRの対象は一覧ページと公開記事ページとなります。

次のように変更を加えましょう。

一覧ページ:

// pages/public/index.tsx

export const getStaticProps: GetStaticProps = async () => {
  const data = await client.getList<Article>({
    endpoint: 'articles',
    queries: {
      fields: ['id', 'private', 'title'],
    },
  });

  return {
    props: {
      data,
    },
    revalidate: 60,    // 追加
  };
};


公開記事ページ:

// pages/public/[id].tsx

export const getStaticPaths = async () => {
  const data = await client.getList<ArticleType>({
    endpoint: 'articles',
    queries: {
      filters: 'private[equals]false',
    },
  });


  const paths = data.contents.map((content) => `/public/${content.id}`);
  return { paths, fallback: 'blocking' };    // 修正
};

export const getStaticProps: GetStaticProps = async (context) => {
  const id = context?.params?.id as string;
  const data = await client.getListDetail<ArticleType>({
    endpoint: 'articles',
    contentId: id,
  });

  return {
    props: {
      data,
    },
    revalidate: 60,    // 追記
  };
};


まず、どちらのファイルにもrevalidate: 60の一行を追加しました。
これにより、最新データを取ってきてから60秒間はそのデータが使い回されますが、それを過ぎると再度取得しにいきます。

また、公開用記事ページのgetStaticPathsreturnにてfallback: 'blocking'を指定しています。
新規で記事を作成した場合は新しいパスが作られるため、通常ビルドなしでgetStaticPathsの値は更新されないのですが、上記のように指定することで、存在しないパスのアクセスが来た場合はサーバーサイドで都度レンダリングが行われます。

ここまで設定すると、管理画面から新しく公開用記事を作成した場合でも、少なくとも60秒間待てば自動的に本番に反映されるようになります。

2. 会員向け記事の見せ方改善

今の状態では、会員向け記事にアクセスすると「ログインが必要です」とだけ表示され、内容については全く分からず、ユーザーが会員になろうという気も起こりません。

そこで、「タイトル」「サムネイル画像」「概要」を表示して、ユーザーが記事に興味を持てるように改善してみます。

まずはmicroCMSにて新しく「概要」フィールドを設けましょう。
「API設定 > APIスキーマ」から設定可能です。

コンテンツ編集画面は次のようになります。

また、変更したスキーマに合わせて型情報も変更します。

// types/index.ts

export type Article = {
  title?: string;
  description?: string;    // 追加
  body?: string;
  thumbnail?: MicroCMSImage;
  private: boolean;
};

次に、会員向け記事のソースコードを変更していきましょう。

import { getSession, Claims, getServerSidePropsWrapper } from '@auth0/nextjs-auth0';
import type { NextPage, GetServerSideProps } from 'next';
import Article from '../../components/Article';
import { client } from '../../libs/client';
import { Article as ArticleType, ArticleListDetail } from '../../types';

export const getServerSideProps: GetServerSideProps = getServerSidePropsWrapper(async (context) => {
  const { req, res } = context;
  const id = context?.params?.id as string;
  const session = await getSession(req, res);

  const data = await client.getListDetail<ArticleType>({
    endpoint: 'articles',
    contentId: id,
  });

  if (!session) {
    return {
      props: {
        data: {
          title: data.title,
          description: data.description,
          thumbnail: data.thumbnail,
        }
      }
    };
  }

  return {
    props: {
      data,
      user: session.user,
    },
  };
});

type Props = {
  data?: ArticleListDetail;
  user?: Claims;
};

const PrivateId: NextPage<Props> = ({ data, user }) => {
  if (!data) {
    return null;
  }
  if (!user) {
    return (
      <main>
        <Article data={data} />
        続きを読むにはログインが必要です
      </main>
    );
  }
  return <Article data={data} />;
};

export default PrivateId;

変更点としては、getServerSidePropsにてセッション情報がない場合でも、title, description, thumbnailを返却するようにしています。
このようにすることで、情報を絞りつつ、ユーザーにとって興味のある表示を行うことができるようになります。

CSSで調整することで次のように見せることができます。

残り文字数表示は、本文の文字カウントを行うことで可能になります。

ソースコード

ここまでのソースコードは下記GitHubで閲覧することができます。
https://github.com/microcmsio/microcms-sample/tree/master/membership-media

おわりに

以上で基本的な部分は完成したと思います。
あとはCSS調整などで自分の好みの形に作り込んでいっていただければと思います。

今回は認証にAuth0というサービスを利用しましたが、どのサービスでも同様のことは実現できるかと思いますので、ぜひいろいろと試してみてください。

まずは、無料で試してみましょう。

APIベースの日本製ヘッドレスCMS「microCMS」を使えば、 ものの数分でAPIの作成ができます。

microCMSを無料で始める

microCMSについてお問い合わせ

初期費用無料・14日間の無料トライアル付き。ご不明な点はお気軽にお問い合わせください。

お問い合わせ

microCMS公式アカウント

microCMSは各公式アカウントで最新情報をお届けしています。
フォローよろしくお願いします。

  • X
  • Discord
  • github

ABOUT ME

柴田 和祈
microCMSのデザイン、フロントエンド担当 / ex Yahoo / 2児の父 / 著書「React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで 」 / Jamstack