Next.jsでGoogleの検索結果をAPIで取得して画面に表示するまで

# 記事内容
Google検索で表示される画像のデータを取得してNext.jsのアプリの画面に表示するまでの方法

# GCP上でAPIと検索エンジンの準備をする
流れとしては自分用の検索エンジンを作って、その検索結果を使うことになる。
https://plus-pm.jp/blog/search-rank-check-use-google-custom-search-api/
この記事に従ってGoogle Custom Search APIのプロジェクトの作成、APIキーの取得、APIの有効化、Custom Search Engine（自分用の検索エンジン）を作成する。

# Google API Client Librariesを使ってAPIを叩く
https://developers.google.com/api-client-library?hl=ja

JavaScriptを選択すると、以下Githubのページに飛ぶ。
https://github.com/google/google-api-javascript-client

READMEの通りに進めていく。
TypeScriptの場合は@types/gapiを使えば良いとのことなので、そっちを使っていく。

```
npm install --save @types/gapi
```

https://developers.google.com/identity/oauth2/web/guides/migration-to-gis?hl=ja
上記のページの通りgoogle-api-javascript-clientは既に非推奨であった。
以前まではGoogleログインプラットフォームライブラリとGoogleAPIクライアントライブラリを使っていたが、これから Google Identity Services ライブラリに移行するらしい。

READMEのOption 2を試してみると、`head`に以下のスクリプトタグを追加する必要があるので
Next.jsの`Head`を使用して、`layout.tsx`に追加。
```
<script src="https://apis.google.com/js/api.js"></script>
```

このスクリプトをそのまま使うと、[Next.jsの非同期スクリプトエラー](https://nextjs.org/docs/messages/no-sync-scripts)が出るため、`async`属性を追加しました。

```jsx
import Head from 'next/head';

const MyHead = () => {
  return (
    <Head>
      <script src="https://apis.google.com/js/api.js" async />
    </Head>
  );
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <Providers>
          <MyHead />
          <div>{children}</div>
        </Providers>
      </body>
    </html>
  );
}
```

最後に、googleapisとaxiosをインストールし、custom searchのURLに各種情報を入れてAPIを叩く。

```
npm install googleapis axios
```

```tsx
"use client";

import styles from "./page.module.css";
import { useState } from "react";
import { Input, IconButton, SearchIcon } from "@/plugins/chakra-ui-modules";
import axios from "axios";
import { customsearch_v1 } from "googleapis";

export default function Vocabulary() {
  const [englishWord, setEnglishWord] = useState("");
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) =>
    setEnglishWord(event.target.value);

  const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
    if (event.key === "Enter") {
      searchEnglishWord();
    }
  };
  const searchEnglishWord = async () => {
    searchImages();
  };

  const [images, setImages] = useState<string[]>([]);

  const searchImages = async () => {
    try {
      const API_KEY = process.env.NEXT_PUBLIC_GOOGLE_SEARCH_API_KEY;
      const CUSTOM_SEARCH_ENGINE_ID =
        process.env.NEXT_PUBLIC_CUSTOM_SEARCH_ENGINE_ID;

      const response = await axios.get(
        `https://www.googleapis.com/customsearch/v1?q=${englishWord}&cx=${CUSTOM_SEARCH_ENGINE_ID}&key=${API_KEY}&searchType=image`
      );

      const items = response.data.items as customsearch_v1.Schema$Result[];

      const imageUrls = items.map((item) => item.link || "");
      setImages(imageUrls);
    } catch (error) {
      console.error("データの取得中にエラーが発生しました:", error);
    }
  };

  return (
    <div>
      <div>
        <Input
          value={englishWord}
          onChange={handleChange}
          onKeyDown={handleKeyDown}
          placeholder="英単語を入力してください。"
        />
        <IconButton
          aria-label="SearchIcon"
          icon={<SearchIcon />}
          onClick={searchEnglishWord}
        />
      </div>
      <div>
        {images.map((imageUrl, index) => (
          <img
            key={index}
            src={imageUrl}
            alt={`image${index}`}
            className={styles.image}
          />
        ))}
      </div>
    </div>
  );
}
```