虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

React.jsの定番フレームワークNext.jsに触れてみよう!

今回は自己研鑽の時間でNext.jsを触ってみたので、紹介したいと思います。

目次

Next.js -- Reactベースのフロントエンドフレームワーク

Reactをベースとした、Node.jsで起動するフロントエンドフレームワーク

https://nextjs.org/

f:id:toranoana-lab:20220325165054p:plain

Next.jsの強み

  • ファイル名から自動でルーティング
  • SSR と SSG をページ毎に設定可能

他にもいくつかありますが、今回はこちらの2つにフォーカスして解説していきたいと思います。

引用 : https://nextjs.org/learn/basics/create-nextjs-app

Next.jsの開発の始め方

おすすめはVercelを利用する方法です。

Vercel -- ホスティングサービス

  • Next.jsの開発元であるVercelが作成したプラットフォーム
  • ビルドやホスティングが可能なサービス
  • 自動SSL化、サブドメインが自由に設定できるなど無料でも機能豊富
  • 無料でテンプレートが複数提供されている

https://vercel.com/

f:id:toranoana-lab:20220325165023p:plain

今回は、Vercelのテンプレートを利用して始めます。

Vercelを利用し、テンプレートを起動

1: https://vercel.com/ へアクセスし、 TryVercel を選択

f:id:toranoana-lab:20220322161512p:plain

2: ログインに使用するアカウントを選択。今回はGithubを選択

f:id:toranoana-lab:20220322161553p:plain

3: ログイン後 Clone Template にてテンプレートをクローンできるので、一番基本となるテンプレートの Next.js を選択

f:id:toranoana-lab:20220322162323p:plain

4: 作成先のリポジトリ名を決め、Createを選択。今回は nextjs にします。

f:id:toranoana-lab:20220322162335p:plain

5: ログインしたGithubプロジェクトにテンプレートをクローンできている事を確認

6: ローカルPCにテンプレートコードをクローンし、インストールと起動を実行

$ npm i
$ npm run dev

7. http://localhost:3000 へアクセスし、テンプレートが表示されれば成功

f:id:toranoana-lab:20220322163115p:plain

これで開発する準備が整いました。

Next.jsの基本的な解説

以下のようなディレクトリ構成をしています。

$ tree
.
├── README.md                                                                                                                                                                
├── next.config.js                                                                                                                                                           
├── node_modules                                                                                                                                                             
├── package-lock.json
├── package.json
├── pages
│   ├── _app.js
│   ├── api
│   └── index.js
├── public
│   ├── favicon.ico
│   └── vercel.svg
└── styles
    ├── Home.module.css
    └── globals.css

テンプレートに表示されている通り、 pages/index.js がページの本体のComponentになります。

# pages/index.js
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'

export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
 
      <main className={styles.main}>
        <h1 className={styles.title}>
          Welcome to <a href="https://nextjs.org">Next.js!</a>
        </h1>

      (省略)

  )
}

Next.jsの強み

ファイル名から自動でルーティング

https://localhost:3000 のにアクセスした時に、 pages/index.js のComponentを呼ぶようにルーティングされています。

これはNext.jsの特徴で、pagesに格納したComponentファイルは、ゼロコンフィグでアプリケーションのルーティングを構成する事ができます。

デフォルトでは pages が対象になっていますが、 src/pages に配置しても動作します。好みで移動させましょう。

静的ページを作成

試しにページを追加してみます。

pages/hoge/fuga.js を作成した場合、 https://localhost:3000/hoge/fuga が自動でルーティングされます。

# pages/hoge/fuga.js
export default function Fuga() {
  return <p>fuga</p>
}

f:id:toranoana-lab:20220322165544p:plain

また、Fast Refreshという機能により自動でファイルの変更やルーティングの追加を反映してくれるのでサーバーを再起動する必要もありません。便利ですね。

f:id:toranoana-lab:20220322165745p:plain

新規コンポーネントへのリクエスト時に自動でコンパイルをしてくれているのが確認できます。

動的ページを作成

ファイル名を [fruit].js のように角括弧で記述することで動的なルート指定が可能になります。

この場合、 pages/:fruit のリクエストがルーティングされます。

next/routeruseRouter 機能を使用することで fruit を取得できます。

# pages/[fruit].js
import {useRouter} from 'next/router'

export default function Fruit() {
  const router = useRouter()
  const {fruit} = router.query

  return <p>あなたの選んだフルーツは{fruit}です!</p>
}

http://localhost:3000/apple でアクセスしてみると、動的にページ生成が行えている事が確認できます。

f:id:toranoana-lab:20220323180438p:plain

Rest APIを作成

同じように、Rest APIの作成も可能です。

テンプレートコードの pages/api/hello.js の中身を確認してみます。

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

jsonで返却するだけでAPIを作ることができます。

自動でルーティングされるので、この場合は https://localhost:3000/api/hello にアクセスすることでjsonで返却ができます。

f:id:toranoana-lab:20220323174845p:plain

簡単なAPIの作成も、特にモジュールなどのインストール無く実装することができます。

SSR と SSG をページ毎に設定可能

一般的なフレームワークは、サービス全体でSSGとするかSSRとするかしか選択できません。

それに対し、Next.jsはページ毎に指定が可能な事が大きな特徴になります。

SSR と SSG について簡単に解説した後、Next.jsでページ毎に設定する方法を解説します。

server-side rendering (SSR)

リクエストを受けてから、サーバーでHTMLを生成する一般的な方法。

最新の情報で常にHTMLを生成できるため、ユーザー認証が必要なページ等に向いています。

ただし、毎回HTMLを生成する関係上レンダリングを毎回行うのでCPU負荷が高く、応答速度も遅めです。

static-side generation (SSG)

サーバー側で事前にHTMLを生成しておき、リクエストを受けた時に返却する方法。

事前にHTMLを生成しているため、応答速度が高くレンダリングを行わないためCPU負荷も低いです。

ただし、事前にビルドしたタイミングでコンテンツを生成している関係上最新のページに更新したい場合はビルドを実行し直す必要があります。そのため、リアルタイム性が求められるページには不向きです。

SSRとSSGのメリット、デメリット

HTML生成方法 リアルタイム性 応答速度
SSR 常にリアルタイム 遅い
SSG ビルド時に依存 早い

特徴が違うため、ページ毎に適切に生成方法を変える事ができれば快適なWebサイトを作成できます。

Next.jsではページ毎に生成方法を指定できるため、実際にやってみます。

Next.jsでページ毎に設定

テンプレートの package.json を確認してみましょう。

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "lint": "next lint"
},

next build のコマンドが存在します。このコマンドを実行する事で、HTMLファイルを事前に静的生成できます。

いくつか静的生成時に使えるAPIを紹介します。

getStaticProps

静的生成時に外部APIを実行し、データを取得できるAPI。

今まで作成していたcomponentと同一ファイルに記述する事で、ビルド時にAPIの実行など外部データの取得を行うことができます。

GitHubAPIを使い、toranoanaの名称を取ってくるコードを書いてみます。

export async function getStaticProps() {
  const res = await fetch('https://api.github.com/users/toranoana');
  const resJson = await res.json();
  return { props: { resJson } }
}

export default function Github(props) {
  return <p>{ props.resJson.name }</p>
} 

ビルドを実行します。

$ npm run build

Page                                       Size     First Load JS                                                                                 
┌ ○ /                                      5.57 kB        76.5 kB                                                                                 
├   └ css/149b18973e5508c7.css             655 B                                                                                                  
├   /_app                                  0 B            70.9 kB                                                                                 
├ ○ /[fruit]                               365 B          71.3 kB
├ ○ /404                                   194 B          71.1 kB
├ λ /api/hello                             0 B            70.9 kB
├ ● /github (370 ms)                       286 B          71.2 kB
└ ○ /hoge/fuga                             269 B          71.2 kB

左側のマークはそれぞれ以下を表しており、 github のアイコンなので静的生成されており、ページ毎に違う生成方法が指定できている事が確認できます。

λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
○  (Static)  automatically rendered as static HTML (uses no initial props)
●  (SSG)     automatically generated as static HTML + JSON (uses getStaticProps)

サーバーを起動し、 http://localhost:3000/github にアクセスする事で静的生成されたtoranoanaの名称が取得できました。

f:id:toranoana-lab:20220324115836p:plain

getStaticPaths

静的生成時に動的ルートのページを生成するAPI。

例えば先程の pages/[fruit].js などを静的生成する場合に使用します。

受け取った文字列をjsonで返却するAPIと、それを呼び出し静的生成するコードを書いてみます。

# pages/api/test/[test].js

export default function handler(req, res) {
  const {                                  
    query: { test }                        
  } = req;                                 
                                           
  res.status(200).json({ "apiName": test })
}                                          
# pages/test/[test].js

export async function getStaticPaths() {                                   
  const params = [                                                         
    { params: { test: "toranoana" } },                                     
    { params: { test: "yumenosora" } }                                     
  ];                                                                       
                                                                           
  return { paths: params, fallback: false };                               
}                                                                          
                                                                           
export async function getStaticProps({ params }) {                         
  const res = await fetch(`http://localhost:3000/api/test/${params.test}`);
  const resJson = await res.json();                                        
  return { props: { resJson } };                                           
}                                                                          
                                                                           
export default function Test(props) {                                      
  return <p>{ props.resJson.apiName }</p>                                    
}                                                                          

npm run build で実行し、2つのページが生成されている事を確認します。

Page                                       Size     First Load JS
┌ ○ /                                      5.57 kB        76.5 kB
├   └ css/149b18973e5508c7.css             655 B
├   /_app                                  0 B            70.9 kB
├ ○ /[fruit]                               365 B          71.3 kB
├ ○ /404                                   194 B          71.1 kB
├ λ /api/hello                             0 B            70.9 kB
├ λ /api/test/[test]                       0 B            70.9 kB
├ ● /github (385 ms)                       292 B          71.2 kB
├ ○ /hoge/fuga                             269 B          71.2 kB
└ ● /test/[test]                           291 B          71.2 kB
    ├ /test/toranoana
    └ /test/yumenosora
  • /test/toranoana
  • /test/yumenosora

が静的生成されている事が確認できます。

今回は固定値を入れましたが、ここを外部APIにする事で記事の数だけ事前生成などが可能になるので非常に便利です。

Vercelにデプロイする

余談ですが、既にVercelからテンプレートコードを取得したのでリポジトリがVercelに認識されサーバーが作成されている状態になります。DOMAINS を選択すると、サーバーが確認できます。

更新をリポジトリに反映することで、Vercel上のサーバーも更新されるため確認してみます。

今までの編集内容を test ブランチに反映し、 main ブランチに向けてプルリクエストを出します。

そうするとVercel上からプルリクエストの確認とプレビューサーバーが確認できるようになります。中身を確認し、今回の編集が反映されているか確認してみましょう。

1: Vercelの Deployments を開き、今回作成したプルリクエストがある事を確認

f:id:toranoana-lab:20220325112637p:plain

2: 選択し、DOMAINSを選択でプレビューサーバーを起動して今回の編集が反映されていることを確認

f:id:toranoana-lab:20220325161959p:plain

無事Vercel上でも作成した api/[fruit] が表示された事を確認できました。

あとはmainブランチにマージすれば、本番デプロイ完了です。

まとめ

Next.jsはシンプルながら、強力なフロントエンドフレームワークです。

本稿で紹介したものは一部にすぎません。Vercelを使うことで気軽に体験する事もできるので、是非簡単なページ作成からNext.jsを試してみて下さい。

P.S.

虎の穴ラボでは、私たちと一緒に新しいオタク向けサービスを作る仲間を募集しています。
詳しい採用情報は以下をご覧ください。
yumenosora.co.jp