NextJS+GASを使って質問スロットを作った話

どうもこんにちは。suginokoです。暑いですね。夏ですね。
今回は勉強がてら作った『質問スロット』をただ作ったよ、っていう記事です。(技術的には大したことない・・・)

## 勉強がてら作りたかった
普段 [弊社](https://brewus.co.jp/) のwebフロントのレギュレーションとしてReactを使って開発を行っておりますが、Reactに限らず他のフレームワーク(以下FW)使ってみたいとか、他の技術も使ってみたいなということで最初はReactのFWであるNextJSを選定。GASについては存在を上司に教えてもらったこともありますし、使ってみよう！ということで。
また、[弊社](https://brewus.co.jp/)ではたまに、社員をごちゃまぜにしてフリートークを行う時間が設けられることがあったりするんで、『質問スロット』使えばコミュ障な私でも会話できるかな・・・？とか考えた結果生まれたものです。

## 開発環境
* Next.js v10.2.0
* React v17.0.2
* TypeScript v4.3.2
* GAS(Google Apps Script) v8


## それぞれの専門用語等
* [Next.js](https://nextjs.org/)
* [Google Apps Script（GAS）とは？できることから始め方までの基本](https://anagrams.jp/blog/google-apps-script/)

## GASの設定
GASでのやりたいこととして以下のことを設けました。
* Google スプレッドシート使って質問項目を記入する
* 質問カテゴリはシートごとに分ける
* 質問カテゴリを選択できる形にしたいので、それぞれの質問シートのデータは配列で取得したい

### スプレットシートに質問事項を書く
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696960/4a30a73a-0f68-0046-8977-f214ae188b5f.png)

こんな感じで書いて、App Scriptに紐づけて連携し、GASもスクリプトエディタで質問事項を取得するスクリプトを書いていきます。

### GASを書く
questionFunction.gs
※v8なのでconstやらletやら使って書いてもよかったと思います。(NextJSとので連携でうまくいかなくてそのままにしてた・・)
GASのAPIにcallbackというqueryを入れた時はjsonpで取得できる形です。(NextJSでは必要なかったけど)

```javascript
function doGet(e) {
  var callback = e.parameter.callback;
  var sheetData = getSheetData();
  var out = null;

  if (callback) {
    var text = callback + '(' + (JSON.stringify(sheetData, null, 2)) + ')';
    out = ContentService.createTextOutput(text);
    out.setMimeType(ContentService.MimeType.JAVASCRIPT);
  } else {
    out = ContentService.createTextOutput();
    out.setMimeType(ContentService.MimeType.JSON);
    // JSONPをセット
    out.setContent(JSON.stringify(sheetData));
  }

  return out;
}

function getSheetData () {
  var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
  var arr = [];

  for (let i = 0; i < sheets.length; i++) {
    var name = sheets[i].getName();
    var sheet = SpreadsheetApp.getActive().getSheetByName(name);
    var rows = sheet.getDataRange().getValues();
    var keys = rows.splice(0, 1)[0];
    var questions = rows.map((row) => {
       let obj = {};
      row.map((item, index) => {
        obj[String(keys[index])] = String(item);
      })

      return obj; 
    });

    var data = {
      sheetTitle: name,
      questions
    };

    arr.push(data)
  }

  return arr;
}
```

getSheetData()でシートの情報を取得します。
apiを叩くときにGETメソッドだとdoGet関数が動きます。(POSTだとdoPost())
ここではスプレットシートのデータをJSONで受け取る形で作りました。
こちらでデプロイをすると「ウェブアプリ」というURL(APIとして使う)が出力されるのでこちらを使います。


## NextJSの設定
fetchを使って取得します。
NextJSの場合はSSG(Static Site Generation＝静的サイト生成)で作れるので、この仕組みを利用して取得します。
(Reactの場合はuseEffectで取得するしかなさそうな感じだったな。。)
SSGを実現するためにgetStaticPropsというものを使います。
getStaticPropsというのはこのアプリケーションをビルドするタイミングでデータを取得しに行って、getStaticPropsから送られるpropsを使って、データを取得していくものです。

```JavaScript
const QuestionSlot: FC = memo((props) => {
// getStaticPropsで返されたpropsを受け取ります
...
})

export const getStaticProps: GetStaticProps<any> = async () => {
　// GASのデプロイ時に生成されるURL
　// redirect: 'follow'入れないと取得できないっぽい
  const res = await fetch('https://script.google.com/hogehoge/exec', { redirect: 'follow' })
  const json = await res.json()

  return {
    props: { json },
    revalidate: 300, // 5分に1回更新
  }
}
```

受け取ったJSONデータは(少し内容は省略)

```JSON
{
	"json": [
		{
			"sheetTitle": "仕事編",
			"questions": [
				{
					"question": "今の仕事で面白いと思っていることはなんですか？"
				},
				{
					"question": "これからやってみたい仕事はなんですか？"
				},
				{
			]
		},
		{
			"sheetTitle": "プライベート編",
			"questions": [
				{
					"question": "最近の休みの日は何をして過ごしていますか？"
				},
				{
					"question": "プライベート何が一番やってて楽しいですか？"
				},
			]
		}
	]
}
```


## 完成したもの
※デザインとかうまくできたらよかったのですが・・・ド下手な絵ですみません・・
![pierp.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696960/09553258-4263-4510-bff1-c404b25b1589.gif)

## おまけ
NextJSではなく、Reactを使う場合(NextJSもReactベースだけども)はJSONP使わないと取得できなそうだな・・という感じでした。
(例えば初回useEffect時にAPI叩くとCROSでエラーになる)
JSONPだとセキュリティ的によろしくないので、うーん・・・と考えさせられますし、TypeScript使っているので、それで弾かれているんでは？と試行錯誤しました。
また、getStaticPropsでAPI取得するとNetworkに繋いだAPIのURL出ないんですね。これはNextJSの素晴らしいところな気がします。
案件でGAS使うことはないかな？というところはありますが、個人で勉強がてら開発する分には色々実験できてよかったかなと思いました。