GreenSnap TECH BLOG

GreenSnapのエンジニアチームの取り組みや使っている技術を紹介します

サーバーサイドエンジニア、Next.jsを触る

はじめに

こんにちは、平川です。普段はサーバーサイドエンジニアとして業務に携わっております。
フロント側は基本的に手をつけない領域ですが、業務でNext.jsを触る可能性が出てきたので、実際に手を動かして知見を得ていこうと思います。

Next.jsとは

Next.jsはReactベースのフロントエンドフレームワークです。
できることは色々あるのですが、大きな特徴としては、

  • SSR/SSG
  • Zero Config
  • File-system Routing
  • Fast Refresh
  • Image Optimization

などが挙げられます(他にも色々あります)

nextjs.org

今回触ってみるもの

個人的に気になった機能がいくつかあるので今回はそれらを実際にコードベースで触ってみて挙動などを理解していきます。

  • Zero Config
    • configファイルがなくても自動コンパイルとバンドルが可能
  • SSR/SSG
    • あらかじめレンダリングして表示
  • API Routes
    • APIバックエンド機能を提供

実際に触ってみる

create next app

まずは開発する環境を作成します。 下記のコマンドを任意のコマンドラインで実行します。今回はTypeScriptに対応したいのでオプションに--tsをつけます

npx create-next-app@latest --ts

コマンドを実行するとアプリ名を尋ねられるので任意の名前をつけます。その後ディレクトリが作成されます。

f:id:orca_gs:20211226120500p:plain
どのWebフロントエンドフレームワークもそうですが、この時点でWebアプリとして実行が可能になります。

// アプリケーションをビルド
npm run build

// localhost:3000で起動
npm run start

起動するとlocalhost:3000でWebアプリが立ち上がっていることが確認できます。 f:id:orca_gs:20211226120914p:plain

Zero Config

文字通りwebpack等の設定ファイルを記述しなくても動かすことができるというものです。
webpack(複数のjsファイルやcssファイル等を1つのファイルにまとめるツール)を扱う際にはwebpack.config.jsに設定内容を記述する必要があるのですが、初学者には少し学習コストが高かったり、設定方法が難しかったりします。
そういった点において複雑な設定をはじめから請け負ってくれることは嬉しいですね。

ただ、業務で扱う上で何かしらの設定を行いたいケースが出てくる場合もあると思います。
その際はnext.config.jsに内容を記述することで自動で読み込み、設定してくれます。
今回はwebpackではありませんが、起動環境毎に環境変数を設定してページ上に表示してみます。
next.config.jsでの環境変数の設定については下記リンクの記事を参考にしました。

lightgauge.net

page/index.htmlnext.config.jsを下記のように追記していきます。

<!-- index.html -->
...
<main className={styles.main}>
        <h1 className={styles.title}>
          Welcome to <a href="https://nextjs.org">Next.js!</a>
        </h1>
      <!-- 追記 -->
        <h2>{process.env.TEST_ENV}</h2>
      <!-- 追記終了 -->
/* next.config.js */
const { PHASE_PRODUCTION_BUILD } = require( "next/constants" ) ;
const { PHASE_PRODUCTION_SERVER } = require( "next/constants" ) ;
const { PHASE_DEVELOPMENT_SERVER} = require( "next/constants" ) ;
const { PHASE_EXPORT } = require( "next/constants" ) ;

/** @type {import('next').NextConfig} */
module.exports = ( phase, { defaultConfig }) => {
  if( phase === PHASE_DEVELOPMENT_SERVER ){
    return {
      env: {
           TEST_ENV : "develop_env"
        }
    }
  } else if( phase === PHASE_PRODUCTION_SERVER ) {
    return {
      env: {
           TEST_ENV : "production_env"
        }
    }
  }else if( phase === PHASE_EXPORT ) {
    return {
      env: {
           TEST_ENV : "export"
        }
    }
  }else if( phase === PHASE_PRODUCTION_BUILD ) {
    return {
      env: {
           TEST_ENV : "production_build"
        }
    }
  }
}

設定できたかどうかを実行してlocalhost:3000で確認してみます。
npm run devを実行してみます。
f:id:orca_gs:20211226125732p:plain

PHASE_DEVELOPMENT_SERVERの時のENVが参照されていることが分かります。
次にnpm run startを実行してみます。
f:id:orca_gs:20211226125937p:plain

PHASE_PRODUCTION_BUILDの時のENVが参照されていることが分かります。

このようにnext.config.jsを利用することで詳細な設定を行うこともできます。

SSR/SSG

そもそもSSRとは何なのかという話になりますが、SSRとはServer-side Renderingの略称です。
従来だとクライアントサイドで画面を描画していたものをサーバー側で行う技術のことです。
メリットとして、初回アクセス時の描画が早い点やSEOに強い点などが挙げられます。

また、SSG(Static Site Generation)という技術もあり、Next.jsの公式サイトではこちらが推奨されています。
理由としてはSSRはリクエスト毎にサーバー側でHTMLが生成されるのに対し、SSGはビルド時にHTMLを生成し、リクエストの際にそれを再利用するため、パフォーマンスがいいという点が挙げられます。

実装してみる

さて、実際にSSRとSSGの実装やパフォーマンスの違いを確認していきたいと思います。

郵便番号検索APIを使って外部データを取得し、郵便番号、都道府県、市区町村を画面に表示するページをSSR、SSGの両方で実装していきます。

zipcloud.ibsnet.co.jp

実装の簡略化のため、使用するAPIのパラメータ(郵便番号)は固定で取得するようにします。 /pageディレクトリ以下に新たにSSR.tsxを作成し、下記のコードを追加します。

function SSR(api: any) {
  return (
    <div>
      <div>郵便番号: {api.data.zipcode}</div>
      <div>都道府県: {api.data.address1}</div>
      <div>市区町村: {api.data.address2}</div>
    </div>
  )
}

export async function getServerSideProps() {
  // 郵便番号検索APIを叩く
  const res = await fetch(`https://zipcloud.ibsnet.co.jp/api/search?zipcode=1000000`)
  const apiData = await res.json()
  // データパラメーター(results[])の受け取り
  const data = apiData.results[0] // 要素1つの配列のためインデックス0で受け取り
  return { props: { data } }
}

export default SSR

ここでキモになるのが、getServerSideProps()です。
この関数を置いてあげることで、Next.js側はサーバーサイドレンダリングを行ってくれます。 実際に動かしてみると単に取得したデータを表示するページができました。
f:id:orca_gs:20211226165838p:plain

同様にSSGで実装してみます。/pageディレクトリ以下にSSG.tsxを作成します。

// function部分はメソッド名のみSSGに変更、内容は同じ
...
// 以下を変更
export async function getStaticProps() {
  const res = await fetch(`https://zipcloud.ibsnet.co.jp/api/search?zipcode=1000000`);
  const apiData = await res.json();
  const data = apiData.results[0]

  return {
    props: {data}
  };
}

export default SSG

実行結果はSSRと変わらないので省略しますが、getStaticProps()関数を置くことで、Next.js側はSSGと認識して動作してくれます。

パフォーマンスの違い

先の文でも記述していますが、SSRとSSGの違いはリクエスト毎にHTMLを生成しているのかどうかです。
Chromeのシークレットモードで検証した結果が以下のとおりです。

SSRでの結果 f:id:orca_gs:20211226170825p:plain

SSGでの結果 f:id:orca_gs:20211226170744p:plain

どちらも同じ表示なので見えづらいですが、SSRの表示読み込みが1.91sに対し、SSGが186msと圧倒的な速さを見せております。
再リロードして検証してみてもSSRは856ms、SSGは50msとなっておりました。

API Routes

Next.jsはフロントエンド向けのフレームワークですが、APIの構築も可能となっております。
とはいっても標準で同一オリジンからしかAPIを受け付けていません。CORSのミドルウェアをラップしてあげれば動くみたいです。

/page/apiディレクトリ以下にファイルを配置してあげるとpageの代わりにAPIのエンドポイントとして認識してくれます。
create next appした際に既に/page/api/Hello.tsxが存在しており、/api/helloでJSONが返ってくるようになっています。

サンプルを参考にして、メソッド毎にレスポンスを変える実装をしてみます。

/page/apiディレクトリ以下にcomment.tsxを作成し、下記の内容を追加します。

// comment.tsx
import type { NextApiRequest, NextApiResponse } from 'next'

type Data = {
  comment: string
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
  if (req.method === 'POST') {
    res.status(200).json(req.body)
  } else {
    res.status(200).json({comment: "comment"})
  }
}

コードを見て分かるようにPOSTメソッドの際はリクエストの内容を単純にオウム返しするだけとなっており、それ以外は固定値を返すようにしています。
Postmanを使ってAPIを叩いてみます。

POSTメソッドの結果 f:id:orca_gs:20211226175730p:plain

ちゃんとbodyの中身がJSONの形式で返ってきていることが分かります。

POST以外の結果 f:id:orca_gs:20211226175920p:plain

hello.tsxの内容を参考にしているので当たり前ですが、固定のレスポンスが返ってきているのが分かります。

利用シーンとしては、フロントエンド開発時のモックAPIとしての活用ができそうな気がしました。

まとめ

  • Next.jsはReactベースのフロントエンドフレームワークだよ。
  • Zero Configで設定を意識せずに動かすことができるよ。
  • SSR/SSGでHTMLをサーバー側で描画できるようになるよ。Next.jsはパフォーマンスの向上を理由としてSSGを推奨しているよ。
  • Next.jsをAPIサーバーとしても利用することができるよ。ただし、標準では同一オリジンしか叩けないよ。

久々にフロントエンド系のフレームワークに触れてみましたが、機能や考え方などが変わってきていることを身を持って実感しました。
Next.jsとしての機能はほんの一部をかじった程度に過ぎないので、これからも継続して勉強を行い、ブログのネタにできたらと思います。

最後に

弊社では絶賛エンジニア募集中です。BtoCのサービス開発をしてみたい方や、植物に興味のある方は是非応募してください。 カジュアルに話だけでも聞きたいという方もお待ちしてます。 www.wantedly.com