キカガク プラットフォームブログ

株式会社キカガクのプラットフォームブログです。エンジニアやデザイナー、プロダクトマネージャーなどが記事を書いています。

Next.js x Google スプレッドシートで静的サイトをつくる

  • この記事は Next.js を使っている方を対象にしています。
  • 物話は少しフィクションです。

始まり

ある日、お世話になっているお店の店主からこんな依頼がありました。

  • パソコン得意なんだよね、うちの店のホームページ作ってよ!
  • お知らせと商品情報は更新出来るようにしたい!
  • WordPress… なにそれ? Google スプレッドシートだったら使ったことあるよ。

こういう場合はノーコードツールが望ましいかと思ったのですが、Google スプレッドシートCMS 替わりに出来たら何かと便利だと思い、実際に作ってみることにしました。

構成

  • 普段キカガクの業務でも使っている Next.js と、GAS を使います。
  • スプレッドシートの内容を読み取るウェブアプリを GAS で作り、それをビルド時に取得するイメージです。

Google スプレッドシート(GSS)を作る

  • 今回は個人商店のホームページなので、「ニュース」と「商品」の2つのテーブルが必要です。
  • GSS を新規作成し、シートを2つ作ります。
  • 一行目にフィールド名、二行目以降にデータを入れていきます。

Google Apps Script (GAS) で Web アプリを作る

  1. 作成した GSS の上部ナビ > 拡張機能 > Apps Script から GAS の編集画面を開きます。
  2. 適当なプロジェクト名をつけて、関数を作成します。

    • 以下は、指定シートのデータを json 形式で取得する Web アプリ用の関数です。
     jsx
     function doGet(e) {
       Logger.log(e);
    
       const sheetName = e.parameter.sheetName;
       const ss = SpreadsheetApp.getActiveSpreadsheet();
       const sheet = ss.getSheetByName(sheetName);
       if (!sheet) {
         return ContentService.createTextOutput("Sheet not found")
           .setMimeType(ContentService.MimeType.TEXT);
       }
    
       let values = sheet.getDataRange().getValues();
       const headers = values.shift();
    
       let filteredData = values.map(row => {
         let obj = {};
         row.forEach((value, index) => {
           obj[headers[index]] = value;
         });
         return obj;
       });
    
       const result = {};
       result[sheetName] = filteredData;
    
       return ContentService.createTextOutput(JSON.stringify(result))
         .setMimeType(ContentService.MimeType.JSON);
     }
    
  3. GAS の Web アプリをデプロイします。

    1. 右上のデプロイをクリック

    2. 新しいデプロイ > ウェブアプリ を選択

    3. アクセスできるユーザーを「Google アカウントを持つ全員」に設定し、

    4. ウェブアプリのURLが表示されるのでアクセスします。
      • Sheet が見つからないと出てくるので、URLの末尾にシート名を指定します。
        https://script.google.com/macros/s/【idを入力】/exec?sheetName=news
      
      • すると、json 形式で GSS のデータが取れるようになりました。

Next.js で取得する

  • ウェブアプリの情報を、Next.js でビルド時に取得するようにしたいと思います。

      //src/pages/index.tsx
    
      const fetchGss = async (sheetName: string): Promise<any[]> => {
        const path = `https://script.google.com/macros/s/【idを入力】/exec?sheetName=${sheetName}`;
        const res = await fetch(path);
        return await res.json();
      };
    
      async function getProps() {
        let data = {};
        try {
          data = await fetchGss('news');//ニュースを取得
        } catch (error) {
          data = { error: JSON.stringify(error) };
          console.error('Error fetching data:', error);
        }
        return data;
      }
    
      const Home = async () => {
        const data: any = await getProps();
    
        return (
          <div>
            <p>{JSON.stringify(data)}</p>
          </div>
        );
      };
    
      export default Home;
    
  • デプロイ周りは割愛しますが、今回は Vercel でデプロイしました。デプロイ後の Web ページを見てみると、GSS のデータが表示出来ています。
  • ここまでくれば、後は整形してあげるだけで立派なホームページになるはずです。

セキュリティ対策

  • ほぼ完成しているものの、今は GAS で作成したウェブアプリが誰でも見えてしまう状況なので対策を入れます。
  • リクエスト時パラメータのトークンが一致しないとデータが取得できないようにします。
  • GAS の設定画面 > スクリプトプロパティに、SECRET_TOKEN というプロパティでランダムな文字列を追加します。
  • GAS の関数を、トークンが一致しないと「権限無しページ」を返すように修正します。

      jsx
      function doGet(e) {
        Logger.log(e);
          //追加ここから
        const token = PropertiesService.getScriptProperties().getProperty("SECRET_TOKEN")
        if (e.parameter.token !== token) {
          return ContentService.createTextOutput("Unauthorized").setMimeType(ContentService.MimeType.TEXT);
        }
        //追加ここまで
      }
    
  • Vercel の環境変数に、トークンを設定します。
  • Next.js のリクエスト関数も、トークンをパラメータに入れるように修正します。

      //src/app/page.tsx
      const fetchGss = async (sheetName: string): Promise<any[]> => {
        const path = `https://script.google.com/macros/s/【idを入力】/exec?token=${process.env.SECRET_TOKEN}sheetName=${sheetName}`;
        const res = await fetch(path);
        return await res.json();
      };
    
  • これで、トークン無しでアクセスした場合は権限無しページに遷移するようになりました。

更新が簡単に出来るようにする

  • これでうまくいった…と思ったのもつかの間、店主がGSSを更新した後にビルドし直さないと更新は反映されません。
  • 店主へ Vercel にログインしてリビルドしてもらうのはあまり現実的ではないので、簡単にリビルドできる仕組みを作ります。
  • Vercel のデプロイフックを活用します。
  • Vercel > Setting > Git > Deploy Hooks
  • 任意のフック名を入れて Create すると、デプロイフックのURLが取得出来ます。
  • 先程使ったスプレッドシートに新しいシートを作ってボタンを挿入し、
  • 以下の様なデプロイフックを呼び出すGASを作り、ボタンのスクリプトに指定します。

    function deployVercel(confirm = true) {
    if (confirm) {
      var response = Browser.msgBox('Deploy', 'サイト環境を更新しますか?', Browser.Buttons.YES_NO);
      if (response != 'yes') {
        return;
      }
    }
    var urlDev = 'デプロイフックのURL';
    sendDeployRequest(urlDev);
    }
    function sendDeployRequest(url) {
    var options = {
      'method' : 'post',
      'contentType': 'application/json',
    };
    try {
      var response = UrlFetchApp.fetch(url, options);
      Logger.log(response.getContentText());
    } catch (error) {
      Logger.log(error.toString());
    }
    }
    
  • これでスプレッドシート更新後、ボタンを押すだけでサイトがビルドされ、更新できるようになりました。

おわりに

  • 今回は業務外での取り組みでしたが、プラットフォーム部では日々新しい技術を用いての課題解決に取り組んでいます。
  • もし興味を持っていただける方がいらっしゃったら、気軽にカジュアル面談をお願いします!