自分が運営している団体WebをプレーンからNext.jsに変えた話

# はじめに

この記事は、Qiita Advent Calendar 14日目の記事です。
以下の記事より、すべての記事をご覧になれます。

https://qiita.com/0_terarin_0/items/0f367210e2465f7772c9

# 筆者について

- 現在高校２年生
- 水泳部マネージャー
- 生徒会長
- とある団体の代表

# 本編

## 経緯

前にもちょろっと何度か話したことがあるのですが、現在「テラピコ村」という名前で「より良い未来への道作り」というモットーのもと、さまざまな活動をさせていただいてます。現在はビーチクリーニング、アクセサリーづくり、YouTubeでの環境問題啓発[^1]の３本立てで活動しています。よければ、下記のInstagramをご覧ください。

https://www.instagram.com/_village_trpk

さて、この団体ですが、設立当初からWebページを持っていたのですが、全てプレーンで書いていました。というのも、そもそも当時の僕はReactやVueを知らなかったですし、そんな時間も決してありませんでした。僕のQiitaでもこのページの多言語対応について記事を書いたことがあります。

https://qiita.com/0_terarin_0/items/7ddccda3368296401d05

ですが、いろいろ問題がありました。例えば、記事を増やすほど```/blog/```のトップ画面を変更しなければなりません。こんな感じでメンテナンス性が~~クソ~~だったので、サーバーを変えるにあたりNext.jsに乗り換えることにしました！ちなみにサーバーはAWS Amplifyです。

https://aws.amazon.com/jp/amplify/

今回は、以降にあたり問題があった箇所をいくつか載せたいと思います。

## 問題1：ネット上にあるi18nの対応方法がNext.js 13以降に対応してない

今回初めて知ったのですが、App RouterがNext.jsの13以降に搭載されているのでどうやら今までの方法だったらできないそうです。なので、探しに探しまくって以下に記されている記事を参考にさせていただきました。

https://locize.com/blog/next-app-dir-i18n/

この通りにしたら、きちんとできるようになりました！コンポーネントに関しても詳しく記載されていてとてもわかりやすいですね。

## 問題2：ブログの管理方法

さて、ブログの管理方法です。どうやって管理しましょうか？

各記事はMarkdownで書いてますので、これをMarkdown Paserに渡せばいいだけです。非常に簡単ですね！

- react-markdown

https://github.com/remarkjs/react-markdown

- remark-gfm

https://github.com/remarkjs/remark-gfm

- remark-math
- rehype-katex

https://github.com/remarkjs/remark-math

```javascript:main.js
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";

export const Page = () => {
  return(
    <ReactMarkdown
          remarkPlugins={[remarkGfm, remarkMath]}
          rehypePlugins={rehypeKatex}
          components={markdownRule}
        >
      {/*Insert Markdown*/}
      </ReactMarkdown>
  )
}

```

このMarkdownは```fetch()```で取得します。ここでポイント。**もしファイルを更新する可能性がある場合は```{ cache: "no-store" }```をオプションに追記してください。**

```javascript:main.js
async function getMarkdown(url) {
  const res = await fetch(url, { cache: "no-store" });
  const result = await res.text();
  return result;
}
```

さて、ここまでは問題ありません。問題はブログの記事一覧をどのように表示するかです。ここで考えたのは、将来的には全ての作業を自動化したいので、できるだけ普通のユーザーでも使いやすいような仕組みにしたいということでした。そこで、本団体ではGoogle Workspaceを利用しているので、Google スプレッドシートを利用した記事管理をしました。

まず、以下のようなテーブルをスプレッドシートで作成します。

|ID|記事名|日付|作者|カバー画像|Markdown_Ja|Markdown_En|
|---|---|---|---|---|---|---|
| | | | | | | |

そして、ここに追記していったものをJSONで返すAPIを作成します。Google CloudのCloud Functionを利用しました。

```python:main.py
import functions_framework
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google.oauth2 import service_account
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

SCOPES = ["https://www.googleapis.com/auth/spreadsheets.readonly"]

SPREADSHEET_ID = "1diZUm6lg5hF2hR0SPn2Kk_401iASKv6YHW4uixMg7k0"
RANGE_NAME = "Data"

SERVICE_ACCOUNT_KEY = {
  "type": "",
  "project_id": '",
  "private_key_id": "",
  "private_key": "",
  "client_email": "",
  "client_id": "",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "s",
  "universe_domain": "googleapis.com"
}

def hello_http(request):
  creds = service_account.Credentials.from_service_account_info(SERVICE_ACCOUNT_KEY)
  scoped_creds = creds.with_scopes([
    "https://www.googleapis.com/auth/spreadsheets.readonly"
  ])
  service = build("sheets","v4",credentials=scoped_creds)
  sheet = service.spreadsheets()
  result = (
        sheet.values()
        .get(spreadsheetId=SPREADSHEET_ID, range=RANGE_NAME)
        .execute()
    )
  values = result.get("values")
  return values

if __name__ == '__main__':
  hello_http("test")
```

ちなみに、どうやらCloud Functionから実行するときはクレディンシャルが必要ないそうなのですが、方法が分からなかったのでクレディンシャルを利用した認証を利用しています。クレディンシャルの利用については以下の記事を見てください。

https://cloud.google.com/docs/authentication/production?hl=ja#auth-cloud-explicit-python

なお、JSONの内容を全部コピーして環境変数に埋め込むことをおすすめします。そのため、```service_account.Credentials.from_service_account_json()```ではなく```service_account.Credentials.from_service_account_info()```を利用しています。また、記事等の関連ファイルは同じくGoogle CloudのCloud Storageに保存しているのですが、ここでフォルダ名とID名を同じにするとその後の処理が楽になります。

あとはこのAPIが返すJSONをデコードすれば終わりです。

# 最後に

今回は初めてNext.jsを利用した時に迷ったポイントを整理してみました。今後も改善していく必要がありますので頑張っていきたいと思います。それでは、引き続き今後のアドカレ 23 の記事をお楽しみにしてください。

[^1]: 本団体では一つの意見に偏った宣伝はなく、色々な意見を集めた上で視聴者に問いかけるようなコンテンツを作成しています。某環境活動家とかとは違った活動をしているということはご承知いただけるとありがたいです。