Next.jsとGoogleスプレッドシートを連携させて動的ページを作ってみた

Googleスプレッドシートをデータベースとして扱い､スプレッドシートの値をWebサイト上に表示したくなるときありますよね(？)

今回は次のようなアプリを作ってみました！

- 会社情報をスプレッドシートで管理する
- 会社情報が追加(行の追加)されるとページを生成する
- Next.jsのDynamic Routesで動的にページを生成する

`http://XXXXXX/company/a-company`にアクセスすると各社の情報が見れるようになります｡
![CleanShot 2023-12-03 at 09.55.48@2x.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592040/4f78956e-c032-06ea-9119-46cf34cf461a.jpeg)


 ## Googleスプレッドシートの用意
 下記のようなシートを作成します｡
![CleanShot 2023-12-02 at 10.10.16@2x.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592040/e7cd7d26-3acb-d8bf-4f51-fa5fce83e572.jpeg)

次にGASに下記のコードを追加します｡
```javascript
function readData() {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet1 = spreadsheet.getSheetByName('シート1');
  const lastRow = sheet1.getLastRow();
  const data_range = sheet1.getRange(`A2:D${lastRow}`);
  const data_values = data_range.getValues();

  const return_data = data_values.map(data_row => {
    return {
      id: data_row[0], // スプレッドシートのA列の値を追加
      companyName: data_row[1], // スプレッドシートのB列の値を追加
      companyLogo: data_row[2], // スプレッドシートのC列の値を追加
      sales: data_row[3], // スプレッドシートのD列の値を追加
    }
  });
  console.log(return_data) 
  return return_data;
}
 
function doGet() {
    const data = readData();
    const response = ContentService.createTextOutput();
    response.setMimeType(ContentService.MimeType.JSON);
    response.setContent(JSON.stringify(data));
    return response;  
}
```
こちらのコードは下記の記事を参考にしております｡

https://b-risk.jp/blog/2022/11/js-spreadsheet/

`readData`関数でスプレッドシート内のデータを取得してjson形式でデータを返しています｡
`return_data`の中身がこちらになります｡

```javascript
[ { id: 'a-company',
    companyName: 'A農業',
    companyLogo: '',
    sales: 300 
  },
  { id: 'b-company',
    companyName: 'B工業',
    companyLogo: '',
    sales: 400 
  },
  { id: 'c-company',
    companyName: 'C Tech Inc',
    companyLogo: '',
    sales: 1000 
   } 
]
```

`doGet`関数内の`ContentService`でGASで表示されるURLでテキストを確認できるようになります｡
右上の｢デプロイ｣のボタンを押します｡
![CleanShot 2023-12-03 at 06.24.50@2x.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592040/a3dcb3e8-4e90-871f-aa2c-75b9360bcd6b.jpeg)
新しいデプロイをすると､URLが発行されてそのURLにアクセスすると先程確認したJSON形式のデータがブラウザで確認できるようになります｡

あとは､Next.jsでそのURLからデータをフェッチして画面に表示させることができれば完成です｡

 ## Next.jsで動的にページを生成する
 ### プロジェクトの作成
 `Next.js`のプロジェクトを作成します｡
 プロジェクトの作り方は公式ドキュメントを参考にしてもらえば問題ないかなと思うのでここでは､省略します｡
[公式ドキュメント - Next.js Getting Started Installation](URL "https://nextjs.org/docs/getting-started/installation")

### 動的ページの作成
まずは､動的ページのテンプレートを作成します｡下記のようにページを作成します｡
[公式ドキュメント - Next.js / Dynamic Routes](URL "https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes")
```
src
└ app
　 └ company
　 　 └ [slug]
    └ page.tsx
    
```

`page.tsx`には一旦下記のように記述しておきましょう｡
```typescript
export default function Page({ params }: { params: { slug: string } }) {
  return <div>Company Name: {params.slug}</div>
}
```

この構成でページを作成することで下記のようなURLにアクセスしたときにページを表示させることができます｡
```
http:XXXX.com/company/a-works
http:XXXX.com/company/b-works
```
ブラウザでの表示
![CleanShot 2023-12-02 at 08.33.30@2x.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592040/f4f98135-e8fb-ad38-f419-dbc8394851d0.jpeg)

次にGoogleスプレッドシートからデータを取得してくる処理を記述します｡

```react
// src/app/company/[slug]/page.tsx

'use client'
import React, { useState, useEffect } from 'react';

export default function Page({ params }: { params: { slug: string } }) {
  useEffect(() => {
    fetch('発行されたURL')
      .then(response => response.json())
      .then(data => {
        console.log(data);
      })
      .catch(err => {
        console.log(err);
      });
  }, []);

  return (
    <section>
      <div>Company Name: {params.slug}</div>
    </section>
  )
}
```
`console.log(data);`で確認するとデータが入ってきているのがわかります｡
![CleanShot 2023-12-03 at 09.01.14@2x.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592040/3373a5ea-a14c-223d-edd8-5d89bc4be8bb.jpeg)

`useState`で会社情報をさせます｡
また､今回は会社情報の詳細ページを表示させたいのでfindで検索しています｡

```react
'use client'
import React, { useState, useEffect } from 'react';

export default function Page({ params }: { params: { slug: string } }) {
  const [company, setCompany] = useState(null);
  
  useEffect(() => {
    fetch('発行されたURL')
      .then(response => response.json())
      .then(data => {
        const selectedCompany = data.find(c => c.id === params.slug);
        setCompany(selectedCompany);
      })
      .catch(err => {
        console.log(err);
      });
  }, [params.slug]); 

  if (!company) {
    return <div>Loading...</div>;
  }

  return (
    <section>
      <div>Company Name: {params.slug}</div>
    </section>
  )
}
```
あとは`return`文の中に`company`に保持されている情報を出力させます｡

:::note info
tailiwindで適当にスタイリングしています｡
:::

```react
'use client'
import React, { useState, useEffect } from 'react';

export default function Page({ params }: { params: { slug: string } }) {
  const [company, setCompany] = useState(null);
  
  useEffect(() => {
    fetch('https://script.google.com/macros/s/AKfycbz7ZSP4g9roCTeTOYpL7RjWHO2E2oFo48bVT-4SdHk8ak8IIoyXbu9DK6FBN2iVTyqi/exec')
      .then(response => response.json())
      .then(data => {
        const selectedCompany = data.find(c => c.id === params.slug);
        setCompany(selectedCompany);
      })
      .catch(err => {
        console.log(err);
      });
  }, [params.slug]); 

  if (!company) {
    return <div>Loading...</div>;
  }

  return (
    <section className="">
      <div className="max-w-md rounded overflow-hidden shadow-lg p-4 bg-white shadow-lg">
        <h1 className="font-bold text-2xl mb-2 text-gray-700">会社名：{company.companyName}</h1>
        <div className="flex justify-center">
          <img className="w-96" src={company.companyLogo} alt={`${company.companyName} logo`} />
        </div>
        <p className="font-bold text-2xl mb-2 text-gray-700 mt-4">売上: {company.sales}円</p>
      </div>
    </section>
  )
}
```

こんな形で各ページを作成することができました｡(左から下記のURLにアクセスしたときの表示)
- `http://XXXXXX/company/a-company`
- `http://XXXXXX/company/b-company`
- `http://XXXXXX/company/c-company`
![CleanShot 2023-12-03 at 16.54.11@2x.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592040/8adcacce-4768-024c-7357-757392104223.jpeg)

## まとめ
意外と簡単にできたし､アイディア次第ではわざわざ大掛かりな開発をしなくても､少工数でやりたいことを実現できることがありそうだなと感じました｡

:::note
ちなみに､今回使った会社のロゴはChatGPTに生成してもらいました｡
:::