ゼロからの開発日誌

日々の学びなどを中心に

Next.jsチュートリアル目次と学習内容

タイトル 学習内容
1 Create a Next.js App Next.jsアプリの作成方法
2 Navigate Between Pages Next.jsにおけるページ遷移方法など React.jsを拡張したNext.jsの<Link />タグなどを使うことでレスポンス向上できるなど
3 Assets, Metadata, and Css 画像やCSSの適用方法などについて
4 Pre-rendering and Data Fetching Next.jsの特色であるPre-rendering機能について
5 Dynamic Routes 各ページのURLを動的に生成しする方法
6 API Routes APIのルートについて
7 Deploying Your Next.js App Nex.jsアプリのデプロイ方法

その5 Dynamic Routes

5-1. Dynamic Routes

  • 個々のブログページのURLはブログのデータに依存するようにしたいので、動的なルートを使用する
    • 任意のページのURLがデータに依存する場合は、dynamic routesを導入する

学べること

  • getStaticPathsを使用してdynamic routesでページを静的に生成する方法(5-3, 5-4)
  • ブログ投稿ごとにデータを取得するためのgetStaticPropsの書き方(5-5)
  • remarkを使用してマークダウンをレンダリングする方法(5-6)
  • 日付文字列をきれいに表示する方法(5-7)
  • 動的ルートを使用してページにリンクする方法(5-8)
  • 動的ルートに関するいくつかの有用な情報(5-9)

5-2. Steup

5-3. Page Path Depends on External Data

  • 前回のレッスンでは、ページのコンテンツが外部データに依存するケースを取り上げました。
  • インデックスページをレンダリングするために必要なデータをフェッチするために、getStaticPropsを使用しました。

  • このレッスンでは、各ページのパスが外部データに依存している場合について説明します。

  • Next.jsでは、外部データに依存するパスをもつページを静的に生成することができます。これによりNext.jsでは動的URLの利用が可能になります。

外部データに依存するページパス

  • 外部データ(データベースなど)に依存するページをプリレンダリングできる
  • 外部ページからidを引っ張り出して、/posts/[id]のようなページを生成する

ダイナミックルートでページを静的に生成する方法

  • ブログ投稿のための動的なルートを作成したい
    • 各投稿は/posts/<id>というパスを持ち、
    • <id>はトップレベルのpostsディレクトリの下にあるマークダウンファイルの名前とする
    • ssg-ssr.mdpre-rendering.mdがあるので、パスは/posts/ssg-ssr/posts/pre-renderingにしたい

ステップの概要

  • 以下のステップを踏むことで実現できます。まだ変更する必要はありません。次のページですべて行います。
  • まず、pages/postsの下に[id].jsというページを作成する(Next.jsでは、[で始まり]で終わるページがダイナミックルートになる)
  • pages/posts/[id].jsには、これまで作成した他のページと同じように、投稿ページをレンダリングするコードを記述します。
import Layout from '../../components/layout';

export default function Post() {
  return <Layout>...</Layout>;
}
  • さて、ここからが新機能です。
  • このページからgetStaticPathsという非同期関数をエクスポートします。
  • この関数では、idに対して取り得る値のリストを返す必要があります。
import Layout from '../../components/layout';

export default function Post() {
  return <Layout>...</Layout>;
}

export async function getStaticPaths() {
  // Return a list of possible value for id
}
  • 最後に、getStaticPropsを再び実装する必要があります
  • 今回は、与えられたidを持つブログ記事に必要なデータを取得します。
import Layout from '../../components/layout';

export default function Post() {
  return <Layout>...</Layout>;
}

export async function getStaticPaths() {
  // Return a list of possible value for id
}

export async function getStaticProps({ params }) {
  // Fetch necessary data for the blog post using params.id
}

[まとめ]

  • /posts/<id>というパスを持つページを静的に生成する(<id>はファイル名から決定され、これを動的と呼んでいる?)
    • 具体的には、/pages/posts/[id].jsというページを作り、下記の内容を含める([]がNext.jsにおいて動的ページであることを示している)
      • このページを描画するためのReact要素
      • idに基づく配列を返すgetStaticPaths関数
      • idを使用して、ブログの投稿データを取得するgetStaticProps関数

5-4. Implement getStaticPaths

getStaticPathsの実装

  • まず、ファイルを設定しましょう。
  • pages/postsディレクトリの中に[id].jsというファイルを作成します。
  • また、pages/postsディレクトリ内のfirst-post.jsを削除してください。これはもう使いません。
  • そして、pages/posts/[id].jsをエディタで開き、以下のコードを貼り付けます。後で...を記入します。
import Layout from '../../components/layout';

export default function Post() {
  return <Layout>...</Layout>;
}
  • 次に、lib/posts.jsを開き、以下のgetAllPostIds関数を一番下に追加します。
  • これは、postsディレクトリにあるファイル名(.mdを除く)のリストを返します。
export function getAllPostIds() {
  const fileNames = fs.readdirSync(postsDirectory);

  // Returns an array that looks like this:
  // [
  //   {
  //     params: {
  //       id: 'ssg-ssr'
  //     }
  //   },
  //   {
  //     params: {
  //       id: 'pre-rendering'
  //     }
  //   }
  // ]
  return fileNames.map((fileName) => {
    return {
      params: {
        id: fileName.replace(/\.md$/, ''),
      },
    };
  });
}

重要: 返されるリストは単なる文字列の配列ではなく、上のコメントのようなオブジェクトの配列である必要があります。 - 各オブジェクトはparamsキーを持ち、idキーを持つオブジェクトを含んでいなければなりません (ファイル名に[id]を使っているからです)。 - そうでないと、getStaticPathsは失敗します。

  • 最後に、getAllPostIds関数をインポートして、getStaticPathsの内部で使用することにします。
  • pages/posts/[id].jsを開き、エクスポートしたPostコンポーネントの上に以下のコードをコピーしてください。
import { getAllPostIds } from '../../lib/posts';

export async function getStaticPaths() {
  const paths = getAllPostIds();
  return {
    paths,
    fallback: false,
  };
}
  • paths は、getAllPostIds() が返す既知のパスの配列を含みます。
  • この配列には pages/posts/[id].js で定義されたパラメータが含まれます。詳しくはpathsキーのドキュメントをご覧ください。
  • fallback: false を無視する - 後で説明します。
  • これでほぼ完成です。しかし、まだgetStaticPropsを実装する必要があります。

次のページでそれをやってみましょう。

5-5. Implement getStaticProps

getStaticPropsの実装 - 指定されたidの投稿をレンダリングするために、必要なデータを取得する必要があります。 - そのためには、lib/posts.jsをもう一度開き、以下のgetPostData関数を一番下に追加します。これは、idに基づいた投稿データを返します。

export function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`);
  const fileContents = fs.readFileSync(fullPath, 'utf8');

  // Use gray-matter to parse the post metadata section
  const matterResult = matter(fileContents);

  // Combine the data with the id
  return {
    id,
    ...matterResult.data,
  };
}
  • 次に、pages/posts/[id].jsを開き、この行を置き換えます。
import { getAllPostIds } from '../../lib/posts';

を以下のコードで実行します。

import { getAllPostIds, getPostData } from '../../lib/posts';

export async function getStaticProps({ params }) {
  const postData = getPostData(params.id);
  return {
    props: {
      postData,
    },
  };
}
  • 投稿ページでは、getStaticPropsのgetPostData関数を使って、投稿データを取得し、propsとして返すようになりました。
  • では、Postコンポーネントを更新してpostDataを使えるようにしましょう。
  • pages/posts/[id].jsで、エクスポートしたPostコンポーネントを以下のコードに置き換えてください。
export default function Post({ postData }) {
  return (
    <Layout>
      {postData.title}
      <br />
      {postData.id}
      <br />
      {postData.date}
    </Layout>
  );
}
素晴らしい!ダイナミックルートの生成に成功しました。

何か問題がありますか?
もしエラーに遭遇したら、ファイルに正しいコードがあることを確認してください。

pages/posts/[id].js はこのようになっているはずです。
lib/posts.jsはこのようになっているはずです。
(それでも動かない場合) 残りのコードは以下のようになっているはずです。
それでもうまくいかない場合は、GitHub Discussionsでコミュニティに質問してください。GitHub にコードをプッシュして、他の人が見られるようにリンクを張ってくれると助かります。

まとめ
繰り返しになりますが、私たちが行ったことをグラフィカルにまとめると、以下のようになります。

動的ルートを持つページを静的に生成する方法

まだ、ブログのマークダウンコンテンツを表示していません。次はこれをやってみましょう。



### 5-6. Render Markdown
### 5-7. Polishing the Post Page
### 5-8. Polishing the Index Page
### 5-9. Dynamic Routes Details

その4 Pre-rendering and Data Fetching

用語

  • Pre-rendering

  • Data Fetching

    • 文字のまま。データをフェッチするという意味。
    • つまり、データを取り込む、どこかからデータをとってくるといった感じ。

4-1. Pre-rendering and Data Fetching

  • 最終的にブログを作成したいが、現時点ではブログのコンテンツがない状態
  • このレッスンでは、外部のブログデータをアプリに取り込む方法を学ぶ
  • このチュートリアルでは、ブログのコンテンツをファイルシステムに格納する
    • コンテンツが他の場所(データベースやヘッドレスCMSなど)に格納されている場合でも同様に実装できる
このレッスンで学ぶこと
  • このレッスンでは、以下について学ぶ
    • Next.jsのpre-rendering機能について
    • 二種類のpre-rendering方式: Static GenerationServer-side Rendering
    • データありとデータなしのStatic-Generation
    • getStaticPropsを使用した、インデックスページへの外部ブログデータのインポート方法
    • getStaticPropsに関するいくつかの有益な情報

4-2. Setup

  • 前のレッスンから続けて受講する場合は、このページを読み飛ばすことができます。

4-3. Pre-rendering

  • データの取り込みについて話す前に、Next.jsの最も重要な概念のひとつであるPre-renderingについて説明する

  • デフォルトでは、Next.jsはすべてのページを事前レンダリングする

  • 事前レンダリングとは

    • クライアントサイドのJavaScriptですべてのHTML生成を行うのではなく、Next.jsがあらかじめ各ページのHTMLを生成しておくことを意味する
    • パフォーマンスとSEOの向上につながります。
  • 生成された各HTMLは、そのページに必要な最小限のJavaScriptコードと関連付けられている

    • ブラウザがページを読み込むと、そのJavaScriptコードが実行され、ページが完全にインタラクティブになる
    • このプロセスは、Hydrationと呼ばれる
プリレンダリングが行われていることを確認する

注意:上記の手順をlocalhostで試すこともできますが、JavaScriptを無効にするとCSSが読み込まれません。

  • アプリがプレーンな React.js アプリ(Next.js を含まない)の場合、プリレンダリングがないため、JavaScript を無効にするとアプリが表示されなくなります。たとえば
    • ブラウザのJavaScriptを有効にして、このページをご覧ください。これは、Create React Appで構築したプレーンなReact.jsのアプリです。
    • ここで、JavaScriptを無効にして、もう一度同じページにアクセスしてみてください。
    • アプリは表示されなくなり、代わりに "You need to enable JavaScript to run this app." と表示されます。これは、アプリが静的なHTMLにプリレンダリングされていないためです。
まとめ:プリレンダリングとプリレンダリングなしの比較

簡単に図式化すると、次のようになります。

  • Next.jsありの場合

Initial Load: 事前読み込みされたHTMLが表示される
JavaScriptファイル読み込み
Hydration: React要素が初期化され、アプリがインタラクティブになる
例えば、インタラクティブな要素である<Link />があった場合、JSファイル読み込み後にアクティブになる

  • Next.jsを使用しない素のReact.jsアプリの場合

Initial Load: 画面には何も描画されない
JavaScriptファイル読み込み
Hydration: React要素が初期化され、アプリがインタラクティブになる(素のReactにはLinkコンポーネントはない)

4-4. Two Forms of Pre-rendering

  • 事前レンダリングには次の2種類があります。
    • ① Static Generation
      • プロダクション環境向けにアプリをビルドした際にHTML生成を行っておき、リクエストがきたらそのHTMLを再利用して返す方式
    • ② Server-side Rendering
      • 各リクエスト毎にHTML生成を行う方式

※留意事項

  • 開発環境(npm run dev実行時)においては、開発しやすくするために①, ②に関わらず、リクエスト毎にページは事前レンダリングされる。
  • 本番環境においては、①の方式を採用していれば、ビルド時に一度だけ事前レンダリングが行われる。

  • Next.jsでは、各ページに対して①, ②のどちらの方式を採用するか選択することができる

  • 一般的な構成ではほとんどのページが①Static Generation、その他のページを②Server-side Renderingで実現される
  • 一度ビルドすればCDNから提供され、各リクエストに対する応答がより高速であるということから可能な限り①を使うことが推奨される
  • ①Static Generationが適している具体なページの例としては下記がある

    • マーケティングページ
    • ブログ投稿ページ
    • Eコマース製品の一覧ページ
    • ヘルプページやドキュメント
  • 「ユーザのリクエストより前にこのページをレンダリングできるか?」という質問にYESなら①を採用すればよく、

  • 逆に、頻繁に更新されるデータを表示するページやリクエスト毎にコンテンツが変化するような場合は②を採用しましょう。
  • 次のレッスンでは、①Static Generationに焦点を当てて、データを伴う場合、データを伴わない場合について解説していきます。

4-5. Static Generation with and without Data

  • Static Generationはデータを伴う場合とデータを伴わない場合に分けて考えることができる
  • ここまでに作成したページは、外部データを取得しないもの

    • このようなページではプロダクション環境向けにアプリをビルドした時に自動的に静的にHTMLが生成されることになります。
  • 一方で、データ取得した後でなければ描画できないページも考えられる

  • 例えば、ビルド時に、ファイルシステムや外部API、データベースなどからデータを取得するような場合だ
  • Next.jsでは、データを伴うページの静的生成(Static Generation with data)にも対応できるようになっている
Static Generation with Data using getStaticProps
  • さて、データを伴うページの静的生成は、どのようにして動くのでしょうか?

    • Next.jsでは、ページコンポーネントと一緒にgetStaticPropsという非同期関数(async function)もエクスポートすることで実現されます。
  • 動作としては、

    • getStaticPropsがプロダクション環境向けのビルド時に実行され
    • 関数内で、外部データをfetchして、そのデータをpropsとしてページに渡せます
export default function Home(props) { ... }

export async function getStaticProps() {
  // ファイルシステム、API、DBなどからの外部データを取得する
  const data = ...

  // `props`をキーとする値が`Home`コンポーネントに渡される
  return {
    props: ...
  }
}

[プログラムの要点] - async functionとしてgetStaticPropsをexportして、その中で外部データを取得して、propsとしてリターンすると、Home componentに渡すことができる

  • 基本的に、getStaticPropsはNext.jsに次のように伝えます。

    • 「おい、このページにはいくつかのデータ依存性があるぞ。だから、ビルド時にこのページをプリレンダリングするときは、最初にそれらを解決しておけよ!」。
  • 注意: 開発モードでは、getStaticPropsは各リクエストで実行されます。

getStaticPropsを使ってみよう
  • 実際にやってみるとわかりやすいので、次のページからはgetStaticPropsを使ってブログを実装していきます。

4-6. Blog Data

シンプルなブログのアーキテクチャを作成する
マークダウンファイルの作成
  • ルートフォルダにnextjs-app/posts/という新しいディレクトリを作成する(これはpages/postsとは別物)
  • nextjs-app/posts/の中に、pre-rendering.mdssg-ssr.mdという2つのファイルを作成する

さて、以下のコードをposts/pre-rendering.mdにコピーしてください。

---
title: 'Two Forms of Pre-rendering'
date: '2020-01-01'
---

Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.

- **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
- **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.

Importantly, Next.js let's you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.

そして、以下のコードをposts/ssg-ssr.mdにコピーしてください。

---
title: 'When to Use Static Generation v.s. Server-side Rendering'
date: '2020-01-02'
---

We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.

You can use Static Generation for many types of pages, including:

- Marketing pages
- Blog posts
- E-commerce product listings
- Help and documentation

You should ask yourself: "Can I pre-render this page **ahead** of a user's request?" If the answer is yes, then you should choose Static Generation.

On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request.

In that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data.
  • Note
    • 各マークダウン・ファイルにはタイトルと日付を含むメタデータ・セクションが上部にある
    • これはYAML Front Matterと呼ばれ、gray-matterというライブラリを使って解析(パース)することができる
gray-matterのインストール
  • まず、各マークダウンファイルのメタデータをパースするためのgray-matterをインストールする
npm install gray-matter
ファイルシステムを読み込むユーティリティ関数の作成
  • ファイルシステムからデータをパースするための、次の処理を行えるユーティリティ関数を作成する
    • 各マークダウン・ファイルを解析し、タイトル、日付、ファイル名(投稿URLのidとして使用)を取得する
    • インデックスページにデータを日付順にリストアップする
    • ルートディレクトリにlib/というトップレベルのディレクトリを作成し、lib/posts.jsというファイルを作成し、以下のコードをコピー&ペーストする
      • ここで作成するlib/命名は任意。慣例としてlibutilsにすることが多い(ちなみに、pages/のようにNext.jsで決まっていて変更不可のものもあるので注意)
import fs from 'fs';  // Note: ファイルシステムからファイルを読み込むためのモジュール
import path from 'path';  // Note: ファイルのパスを操作するためのモジュール
import matter from 'gray-matter';  // Note: マークダウンファイルのメタデータをパースするためのライブラリ

const postsDirectory = path.join(process.cwd(), 'posts');

export function getSortedPostsData() {
  // ディレクトリ"/posts"配下のファイル名取得する
  const fileNames = fs.readdirSync(postsDirectory);
  const allPostsData = fileNames.map((fileName) => {
    // ファイル名をidとして扱うため、不要となる拡張子".md"をファイル名から削除する(正規表現を使用している)
    const id = fileName.replace(/\.md$/, '');

    // マークダウンファイルを文字列として読み込む
    const fullPath = path.join(postsDirectory, fileName);
    const fileContents = fs.readFileSync(fullPath, 'utf8');

    // 投稿のメタデータを解析するために"gray-matter"を使う
    const matterResult = matter(fileContents);

    // 取得したidとデータを紐付ける
    return {
      id,
      ...matterResult.data,
    };
  });
  // 投稿日でソートする
  return allPostsData.sort(({ date: a }, { date: b }) => {
    if (a < b) {
      return 1;
    } else if (a > b) {
      return -1;
    } else {
      return 0;
    }
  });
}
ブログデータの取得
  • ブログデータが解析されたので、それをインデックスページ (pages/index.js) に追加する必要がある
  • これを行うには、Next.jsのデータ取得メソッドであるgetStaticProps()を使用する
  • 次のセクションでは、getStaticProps()の実装方法について学びます。
4-6 まとめ
  • DBの位置付けとしてこのチュートリアルではマークダウンファイルを用意した
  • これらのファイルは、メタデータ(日付、タイトル)を含んでいる
  • マークダウン(のメタデータ部分)を解析するためにgray-matternpm installした
  • 解析処理を行うための関数getSortedPostsDataをエクスポートした
  • ここまでのステップでマークダウンファイルからpages/index.jsに掲載するidとデータを紐付けた内部データを扱えるようになった

  • 次のステップでは、Next.jsのデータ取得関数getStaticProps()を利用する方法を学習する

4-7. Implement getStaticProps

Next.jsのpre-rendering(事前レンダリング)には二通りあり、これらの違いは"HTMLがいつ生成されるか"にある

  • ① Static Generation (静的生成)

    • HTMLをビルド時に生成する方式で、リクエスト毎に再利用される
  • ② Server-side Rendering (サーバーサイドレンダリング)

    • HTMLを各リクエスト毎に毎回生成する方式

※ Next.jsにおいては、多くのページを①で実現し、その他残りのページを②で実現することで描画の効率を高めている

静的生成(getStaticProps())を利用する
  • さて、getSortedPostsDataのimportを追加して、pages/index.jsgetStaticPropsの中で呼び出す必要があります。
  • pages/index.jsをエディタで開き、エクスポートされたHomeコンポーネントの上に以下のコードを追加します。
import { getSortedPostsData } from '../lib/posts';

export async function getStaticProps() {
  const allPostsData = getSortedPostsData();
  return {
    props: {
      allPostsData,
    },
  };
}
  • getStaticPropspropsオブジェクトの中にallPostsDataを返すことで、ブログの記事がpropとしてHomeコンポーネントに渡されることになります。
  • これで、以下のようにブログ記事にアクセスすることができます。
export default function Home ({ allPostsData }) { ... }
  • ブログ記事を表示するために、Homeコンポーネントを更新し、自己紹介のあるセクションの下に、データを含む別の
    タグを追加しましょう。
  • props()から({ allPostsData })に変更することを忘れないでください。
export default function Home({ allPostsData }) {
  return (
    <Layout home>
      {/* Keep the existing code here */}

      {/* Add this <section> tag below the existing <section> tag */}
      <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
        <h2 className={utilStyles.headingLg}>Blog</h2>
        <ul className={utilStyles.list}>
          {allPostsData.map(({ id, date, title }) => (
            <li className={utilStyles.listItem} key={id}>
              {title}
              <br />
              {id}
              <br />
              {date}
            </li>
          ))}
        </ul>
      </section>
    </Layout>
  );
}
  • これで、http://localhost:3000にアクセスすれば、ブログデータが表示されるはずです。

  • おめでとうございます。外部データを(ファイルシステムから)取得し、このデータでインデックスページをプリレンダリングすることに成功しました。

getStaticProps()

Markdownファイルの解析

ページコンポーネントにpropsとして投稿データの配列を渡す

インデックスページにブログ投稿のリストを表示する

  • 次のページでは、getStaticPropsの使い方のコツについて説明します。

4-8. getStaticProps Details

getStaticPropsの詳細
  • getStaticPropsについて知っておくべき重要な情報の紹介
外部APIやデータベースを取得する
  • lib/posts.jsでは、ファイルシステムからデータを取得するgetSortedPostsDataを実装しています。
  • しかし、あなたは外部APIエンドポイントのような他のソースからデータをフェッチすることができ、それはうまく動作します。
export async function getSortedPostsData() {
  // Instead of the file system,
  // fetch post data from an external API endpoint
  const res = await fetch('..');
  return res.json();
}

注意:Next.jsはクライアントとサーバーの両方でfetch()をポリフィルしています。インポートする必要はありません。

  • また、直接データベースに問い合わせることもできます。
import someDatabaseSDK from 'someDatabaseSDK'

const databaseClient = someDatabaseSDK.createClient(...)

export async function getSortedPostsData() {
  // Instead of the file system,
  // fetch post data from a database
  return databaseClient.query('SELECT posts...')
}
  • これは、getStaticPropsがサーバーサイドでのみ実行されるためです。
  • クライアントサイドでは決して実行されません。ブラウザ用のJSバンドルに含まれることもありません。
  • つまり、データベースへの直接問い合わせのようなコードを、ブラウザに送信することなく書くことができるのです。
開発環境と本番環境
  • 開発環境(npm run devまたはyarn dev)では、getStaticPropsはすべてのリクエストで実行されます。
  • 実運用環境では、getStaticPropsはビルド時に実行されます。
  • しかし、この動作はgetStaticPathsが返すフォールバックキーを使って拡張することができます。
  • これはビルド時に実行されるものなので、 クエリパラメータや HTTP ヘッダのようなリクエスト時にしか使用できないデータは使用できません。
ページ内でのみ許可される
  • getStaticPropsは、ページからしかエクスポートできません。ページ以外のファイルから書き出すことはできません。

  • この制限の理由の1つは、Reactはページがレンダリングされる前に必要なデータをすべて持っている必要があるためです。

リクエスト時にデータを取得する必要がある場合はどうすればよいですか?
  • 静的生成はビルド時に一度だけ行われるため、頻繁に更新されるデータや、ユーザーのリクエストごとに変更されるデータには適していません。

  • このような、データが変化する可能性がある場合は、サーバーサイド・レンダリングを使用することができます。

  • サーバーサイド・レンダリングについては、次のセクションで詳しく説明します。
4-8 まとめ
  • getStaticPropsがサーバサイドで実行されるため、ファイルシステムやデータベースからの情報の読み込みを実現できる
  • 開発環境では、getStaticPropsはリクエスト毎に実行されるが、本番環境ではビルド時のみ実行される(ただし、fallbackキーを使って拡張可能)
  • getStaticPropsはページファイルからのみエクスポートできる
  • 頻繁に更新が必要なページにはこの手法は向いていないため、以降に紹介されるServer-sdie Renderingを利用する
    • 言い換えると、ビルド時ではなく、リクエスト時にデータの取得の必要があるならばServer-sdie Renderingを利用すべき

4-9. Fetching Data at Request Time

ビルド時ではなく、リクエスト時にデータを取得する必要がある場合は、サーバーサイドレンダリングを試すことができます。

サーバーサイド・レンダリングを使用するには、ページからgetStaticPropsの代わりにgetServerSidePropsをエクスポートする必要があります。

getServerSidePropsの使用法

getServerSidePropsのスターターコードです。このブログの例では必要ないので、実装しないことにします。

export async function getServerSideProps(context) {
  return {
    props: {
      // props for your component
    },
  };
}

getServerSidePropsはリクエスト時に呼び出されるため、そのパラメータ(context)にはリクエスト固有のパラメータが含まれます。

getServerSideProps は、リクエスト時にデータを取得しなければならないページをプリレンダリングする必要がある場合にのみ、使用する必要があります。TTFB (Time to First Byte) は getStaticProps よりも遅くなります。なぜなら、サーバーはリクエストごとに結果を計算しなければならず、その結果は特別な設定なしにCDNによってキャッシュされることができないからです。

クライアントサイドレンダリング

データをプリレンダリングする必要がない場合は、次の戦略(Client-side Renderingと呼ばれる)を使用することもできます。

外部データを必要としないページの部分を静的に生成(プリレンダリング)します。 ページが読み込まれたら、JavaScriptを使用してクライアントから外部データを取得し、残りの部分にデータを入力します。

この方法は、たとえば、ユーザーのダッシュボード・ページで有効です。ダッシュボードはプライベートなユーザー固有のページなので、SEOは関係なく、ページがプリレンダリングされる必要もありません。データは頻繁に更新されるため、リクエスト時にデータを取得する必要があります。

SWR

Next.jsの開発チームは、SWRと呼ばれるデータフェッチ用のReactフックを作成しました。クライアントサイドでデータを取得する場合は、これを強くお勧めします。SWRは、キャッシュ、再バリデーション、フォーカストラッキング、インターバルでのリフェッチなどを処理します。ここでは詳細を説明しませんが、使用例を示します。

import useSWR from 'swr';

function Profile() {
  const { data, error } = useSWR('/api/user', fetch);

  if (error) return <div>failed to load</div>;
  if (!data) return <div>loading...</div>;
  return <div>hello {data.name}!</div>;
}

詳しくは、SWRのドキュメントをご覧ください。

以上です。 次のレッスンでは、ダイナミックルートを使って各ブログ投稿のページを作成します。

MUIのTypographyでhrefを使おうとしたら"この呼び出しに一致するオーバーロードはありません。"が出て詰まりかけた

タイトルの通りです。

発生状況

Next.jsにMUIを導入し、ResponsiveAppBarのサンプルコードをベースとして、AppBarのボタンにページ遷移用リンクを付与しようとした時に発生した。

補足: 正確にはボタンというよりレスポンシブデザインで画面が小さい場合に左上の「三」から選択できるメニュー

結論

  • 上に書いた状況はあまり関係ないが、単純にTypographyの使い方が間違っていた。
  • Typographyhrefを使うときは、component="a"も同時に定義しないといけなかった。

エラーが出たプログラム

const pageItems = [
  {
    pageTitle: 'About',
    pageUrl: '/about'
  }
];

{pageItems.map((pageItem) => {
  const {pageTitle, pageUrl} = pageItem;
  return (
    <MenuItem key={pageTitle} onClick={handleCloseNavMenu}>
      <Typography 
        href={pageUrl}
        textAlign="center"
      >
        {pageTitle}
      </Typography>
    </MenuItem>
  );
})}

修正したプログラム

const pageItems = [
  {
    pageTitle: 'About',
    pageUrl: '/about'
  }
];

{pageItems.map((pageItem) => {
  const {pageTitle, pageUrl} = pageItem;
  return (
    <MenuItem key={pageTitle} onClick={handleCloseNavMenu}>
      <Typography 
        component="a"  // これが必要だった
        href={pageUrl}
        textAlign="center"
      >
        {pageTitle}
      </Typography>
    </MenuItem>
  );
})}

DockerでNode/Nginx/Janusを単体で動かす。NginxでJanusのデモページをホスティングする。

Node.js

Dockerfile作成

FROM node:16.16.0-alpine
WORKDIR /usr/src/app

RUN apk update && apk add bash

CMD ["/bin/bash"]

ビルド: 上で作成したDockerfileからイメージを作成する

$ docker image build -t node_img:latest .
👇
$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED      SIZE
node_img     latest    13651deec3a6   4 days ago   117MB

コンテナ起動: 上で作成したイメージからコンテナを作成し、起動する

$ docker run --name node_cnt -it node_img
bash-5.1#
bash-5.1# pwd
/usr/src/app
bash-5.1# exit
exit
👇
$ docker ps -a
CONTAINER ID   IMAGE      COMMAND                  CREATED          STATUS                     PORTS     NAMES
6e198779aa4e   node_img   "docker-entrypoint.s…"   17 seconds ago   Exited (0) 9 seconds ago             node_cnt

以上で、docker上でNode.js(bashも使える)環境が整う。

Nginx

Dockerfileの作成

FROM nginx
CMD ["nginx", "-g", "daemon off;"]

ビルド: 上で作成したDockerfileからイメージを作成する

$ docker image build -t nginx_img:latest .
👇
$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx_img    latest    53685b45547d   2 weeks ago   142MB

コンテナ起動: 上で作成したイメージからコンテナを作成し、起動する

$ docker run --name nginx_cnt -d -p 8080:80 nginx_img:latest
571c9fb70e7ad4632384b97c6f4e7cd14dfc1a43887a5966ddd8656a25f6cdb1
👇
$ docker ps
CONTAINER ID   IMAGE              COMMAND                  CREATED         STATUS         PORTS                  NAMES
571c9fb70e7a   nginx_img:latest   "/docker-entrypoint.…"   5 minutes ago   Up 5 minutes   0.0.0.0:8080->80/tcp   nginx_cnt

以上で、docker上でNginx(ブラウザでhttp://localhost:8080/にアクセスするとnginxが起動している)環境が整う。

Janus

Dockerfileの作成

FROM ubuntu:20.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt update 

RUN apt install -y sudo make wget git python3-pip

RUN apt install -y \
      libmicrohttpd-dev libjansson-dev \
      libssl-dev libsofia-sip-ua-dev libglib2.0-dev \
      libopus-dev libogg-dev libcurl4-openssl-dev liblua5.3-dev \
      libconfig-dev pkg-config libtool automake

# libnice
RUN pip3 install meson ninja && \
    cd /root && \
    git clone https://gitlab.freedesktop.org/libnice/libnice && \
    cd libnice && \
    meson --prefix=/usr build && ninja -C build && sudo ninja -C build install

# libsrtp
RUN cd /root && \
    wget https://github.com/cisco/libsrtp/archive/v2.2.0.tar.gz && \
    tar xfv v2.2.0.tar.gz && \
    cd libsrtp-2.2.0 && \
    ./configure --prefix=/usr --enable-openssl && \
    make shared_library && sudo make install

# janus-gateway
RUN cd /root && \
    git clone https://github.com/meetecho/janus-gateway.git && \
    cd janus-gateway && \
    sh autogen.sh && \
    ./configure --prefix=/opt/janus && \
    make && make install && make configs

ENTRYPOINT ["/opt/janus/bin/janus"]

ビルド: 上で作成したDockerfileからイメージを作成する

$ docker image build -t janus_img:latest .
👇
$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
janus_img    latest    36355143bf20   3 minutes ago   819MB

コンテナ起動: 上で作成したイメージからコンテナを作成し、起動する

$ docker run --name janus_cnt -d -p 8088:8088 janus_img:latest
073bb8e0a550ca4216573504760026d5afa1c396becf030e8d3d685545d83b26
👇
$ docker ps
CONTAINER ID   IMAGE              COMMAND                  CREATED         STATUS         PORTS                    NAMES
073bb8e0a550   janus_img:latest   "/opt/janus/bin/janus"   8 seconds ago   Up 7 seconds   0.0.0.0:8088->8088/tcp   janus_cnt

以上で、docker上でJanus(ブラウザでhttp://localhost:8088/janus/infoにアクセスするとJSONが返ってくる)環境が整う。

NginxでJanusのデモページをホスティングする

前提: NginxのDockerfileと同じ階層にhtml/videoroomtest.htmlを配置しておく

JanusとNginxを起動

# Nginxを起動
$ docker run -v `pwd`/html:/usr/share/nginx/html --name nginx_cnt_demo -d -p 8080:80 nginx_img:latest
8b95ca497f08cd7db797354693ef11821add4a599800aa6e51e65e1a32dfbeec

# Janusを起動(デモページのStartボタンが機能するようになる)
$ docker run --name janus_cnt_demo -d -p 8088:8088 janus_img:latest
7a11a39c371a067e290f6750d5eda9cb96f1a2ab910c7bdcbe1c23bbba337749

👇
$ docker ps
CONTAINER ID   IMAGE              COMMAND                  CREATED              STATUS          PORTS                    NAMES
7a11a39c371a   janus_img:latest   "/opt/janus/bin/janus"   About a minute ago   Up 59 seconds   0.0.0.0:8088->8088/tcp   janus_cnt_demo
8b95ca497f08   nginx_img:latest   "/docker-entrypoint.…"   2 minutes ago        Up 2 minutes    0.0.0.0:8080->80/tcp     nginx_cnt_demo

以上で、docker上でNginxでJanusのデモページ(http://localhost:8080/videoroomtest.html)を動かす環境が整う。

Dockerビルド時のtオプションについて

-tオプション

  • -tオプションを利用すると、イメージ名とタグ名を指定できる
  • イメージ名はREPOSITORY列、タグ名はTAG列に表示される名前のこと

    $ docker images
    REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
    
  • -tオプションを指定しない場合、次のようにイメージ名とタグ名に<none>が設定される

    • <none>のままだと管理が大変になるため通常は-tオプションを付けるべき
    $ docker image build .
    $ docker images
    REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
    <none>       <none>    6ee309c7e004   34 minutes ago   112MB
    
  • -t hogeとすると、イメージ名がhogeとなり、タグ名は自動的にlatestに設定される

    $ docker image build -t hoge .
    $ docker images
    REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
    hoge         latest    6ee309c7e004   36 minutes ago   112MB
    
  • -t hoge:latestとすると、イメージ名をhogeに、タグ名を明示的にlatestに設定できる

    $ docker image build -t hoge:latest .
    $ docker images
    REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
    hoge         latest    6ee309c7e004   38 minutes ago   112MB
    
  • -t hoge:v0.1とした場合、イメージ名がhogeで、タグ名はv0.1になる

    $ docker image build -t hoge:v0.1 .
    $ docker images
    REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
    hoge         v0.1      6ee309c7e004   42 minutes ago   112MB
    

Wikiネットワーク?的なものを考えた

少し前から思っているこんなのあったらどうかなってやつ。走り書きメモなので適当です。

自分の知っている分野の外側を知ることって難しいと思っている。
まだ知らないだけで、本当は自分の好奇心を揺さぶるようなことがあるかもしれない。

でもどうやったらそこに到達できるか?これはなかなか難しいのではと感じる。

人間は基本的に言葉を使って思考するのでこれを利用できないか。

そこで思いついたのが、Word2Vecとかなんとかを使って、ネットワークを球体のような感じで表示する。

各ノードはそれに関連したトピックス。

最初の案は自分で言葉をと登録していく方式で、何らかの辞書で関連するノードを計算し近くに表示する。 でも登録するのはめんどくさそうなので、とりあえずwikipediaとかの単語を拾ってきて適当なものを表示する。

これで何がいいかというと、あるキーワードから初めてそれ関連のものを知れるし、球体の反対側、 最初日本にいたとしたら、くるくるっと回してブラジルに位置にある単語は、最初のキーワードから反対(?)の言葉になる。

ってことで自分の知っている世界と反対の世界を知れるって感じで。

役に立つかは分からないけど、なんか作って見てもおもしろそーだなと。