学習向けに Next.js の SandBox をローカルに構築する


こんにちはフロントエンドエンジニアの茶木です。
最近は学習用の Next.js の ローカルの SandBox を育てています。

学習用の開発環境の重要性

実際のプロジェクトのコードは、プロジェクトの進行に合わせて蓄積されます。
あるコードを理解するとき、プロジェクトが成熟しているほど、そのコードは学習難度は高い傾向にあります。その理由はいくつかあります。

  • プロジェクト特有の機能に対応したため
  • やむを得ず標準的でない方法で実装したため
  • 周辺のコードが増えたため


そこで、実際のプロジェクトとは切り離された、学習用の SandBox を育てておくと、軽いフットワークで学習や実験ができると考えます。

今回作る SandBox の概要

  • TypeScript を標準装備の Next.js 環境を作ります。
  • APIのコール周りを準備します。Next.js の API route 機能を利用します。
    (具体的にはGETしたデータを使い、ページ生成を想定した構成にします)

セットアップ

TypeScript を構成に含めた Next.js のセットアップは簡単です。

yarn create next-app --typescript

https://nextjs.org/docs/basic-features/typescript

以上です!簡単ですね。

APIコール

API route

公式のチュートリアル の API route Support を参考にして記載しています。
チュートリアルは JavaScript なので、 型を与えて TypeScript に書き直しています。

pages/api 配下で(たとえば hello.ts )ファイルを作成し、下記のフォーマットでコードを記述すると、 /api/hello にアクセスしたときにAPIとして機能し、JSONが取得できます。サンプルでは handler というメソッド名になっていますが export default していれば任意の名前でOKです。

// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'

export interface HelloRes {
  name: string;
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<HelloRes>
) {
  res.status(200).json({ name: 'John Doe' })
}

https://nextjs.org/docs/api-routes/introduction

汎用的な getAPI の受け取り hook を作る

API route 機能でエンドポイントはできました。

次は、アプリケーション側でAPIをコールして、利用しやすい形でデータを受け取るようにします。以下コードです。(ちょっと独自実装色があるので標準的な手法は研究中です。情報お待ちしてます)

import axios, { AxiosError, AxiosResponse } from "axios";

// MEMO: 標準手法は研究中
export const api = async <Res>(
  method: string,
  query?: unknown,
): Promise<AxiosResponse<Res>> => {
  const instance = axios.create({ baseURL: "/api/" });
  return await instance.get(method, {
    params: query,
  });

api メソッドの解説

クエリを渡してAPIをコールします。結果を非同期(Promise)で return します。

useGetApi メソッドの解説

ページ表示時(初回 コンポーネント render 直後 )に APIをコールし、取得したデータでページを描画するケースは一般的だと思います。そのケースでは React フックを用いると便利です。

以下、apiメソッドを React フックでラップした useGetApi です。
APIから情報を取得する前は、 undefined を返却し取得後は取得データを返します。

import { AxiosResponse } from "axios";
import { useEffect, useState } from "react";
import { api } from "../libs/middleware";

const useGetApi = <Res>(
  getApi: () => Promise<AxiosResponse<Res>>
): Res | undefined => {
  const [data, setData] = useState<Res | undefined>(undefined);
  useEffect(() => {
    getApi().then((res) => setData(res.data));
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  return data;
};

実際にページで使う

export const useHello = (): HelloRes | undefined => {
  const getApi = useCallback(() => {
    return api<HelloRes>("hello");
  }, []);
  return useGetApi(getApi);
};

実際に、個別のAPIをコールする際は、さらにフックでラップします。

export default function HelloPage() {
  const data = useHello();
  if( !data ) return <p>Loading...</p>
  return <p>{ data.name }</p>
}

ラップしたフックから取得した data で、取得前と取得後の処理を書き分けて完成です。

アドバンス

YAGNI の原則に従い、省略していますが、apiメソッドはいくつか改良の余地があります。

エラーハンドリング

 instance.interceptors.response.use(
    (res: any) => res,
    (error: AxiosError) => hundleError?.(error)
  );

instance の箇所に interceptors を挟むことでエラーハンドリングが可能になります。オプショナルの hundleError メソッドを渡せるようにすれば良いでしょう。

クエリではなくURLパスを使った変数の受け渡し

/api/[user_id]/[date]/news

前説:api 以下に作ったディレクトリとファイルの階層構造は APIエンドポイントを生成します。このとき、各ディレクトリ名に [] で囲ったものを使うと変数名として機能します。APIコール側は、ディレクトリ構造に合わせたURLでコールすると、変数を渡せます。

const url = [...(pathes || [], method)].join("/");
return await instance.get(url, {
  params: query,
});

ふまえて、api メソッドにディレクトリの階層構造に対応した変数の配列( ここでは pathes )を渡し、エンドポイントを作成する方法でも値をAPIに渡すよう拡張ができます。

おわりに

繰り返しになりますが、SandBox で気軽に試せる環境を作っておくことで学習や実験が捗るようになりました。

Gaji-Laboでは、React経験が豊富なフロントエンドエンジニアを募集しています

弊社ではReactの知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違うGaji-Laboを味わいに来ませんか?

もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!

求人応募してみる!

投稿者 Chaki Hironori

webライターもやってるフロントエンドエンジニアです。Reactは自信があります。またデザイン畑の出身で、気持ちのいいアニメーションやインタラクティブな表現は丁寧に手掛けます。好きなものは中南米の遺跡で、スペイン語が少しできます。