Nextでサーバー側に処理を集めた実装例

Nextでサーバー側に処理を集めた実装例

2021年3月31日



Nextでサーバー側に処理を集めた実装例

Nextでサーバー側に処理を集めた実装例を紹介します。 実装内容は、前回紹介したAPIを利用したものと機能的には同じものです。 ページの「Get」ボタンを押したら、Firebaseのデータベースのデータのリストを表示するというシンプルなものです。

2つのページで実装

今回は、Webブラウザー側のコードを最小限にしてシンプルな実装にしています。 実装は2つの別々のページで実装します。

  1. ボタンだけのシンプルなページ
  2. Firebaseのコレクションデータを表示するページ

に分けて実装します。実際は、ボタンではなくリンクを使って別のページを読み込むようにしておいて、Firebaseのコレクションデータを表示するページがリクエストされたら、バックエンド(サーバー)側でFirebaseのデータを取得してリストを表示するページを作ってWebブラウザーに送るという仕組みにします。

最初のボタンのページの例です

import * as React from "react";
import Head from "next/head";
class Sample extends React.Component {
  render() {
    return (
      <React.Fragment>
        <Head>
          <title>Create Next App</title>
          <link rel="icon" href="/favicon.ico" />
        </Head>
        <a className="btn btn-primary" href="/list">
          Get
        </a>
      </React.Fragment>
    );
  }
}
export default Sample;

シンプルな実装で、ボタンは実際は「/list」のページへのリンクです。クリックされるとサーバーに「/list」のページをリクエストします。

次に、読み込まれる「/list」の実装例です、

import * as React from "react";
import admin from "../lib/admin";
interface IProps {
  list: Array<any>;
}
interface IState {}
class List extends React.Component<IProps, IState> {
  constructor(props: IProps) {
    super(props);
    console.log("constructor");
  }
  componentDidMount() {
    console.log("did mount");
  }
  render() {
    console.log("Data:", this.props.list, new Date().toString());
    return (
      <React.Fragment>
        <h1>List</h1>
        <table className="table table-hover">
          <thead>
            <tr className="table-primary">
              <th>name</th>
            </tr>
          </thead>
          <tbody>
            {this.props.list.map((item: any) => (
              <tr key={item.id}>
                <td>{item.name}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </React.Fragment>
    );
  }
}
export async function getServerSideProps(): Promise<any> {
  const list: Array<any> = [];
  console.log("Server");
  try {
    const querySnapshot: admin.firestore.QuerySnapshot<admin.firestore.DocumentData> = await admin
      .firestore()
      .collection("test")
      .get();
    for (let i = 0; i < querySnapshot.size; i++) {
      const doc: any = querySnapshot.docs[i];
      list.push({ id: doc.id, name: doc.data().name });
    }
    console.log("return");
    return {
      props: {
        list: list,
      },
    };
  } catch (error: any) {
    return {
      props: {
        list: [],
      },
    };
  }
}
export default List;

ページがリクエストされると、最初に「getServerSideProps()」が呼び出されて、Firebaseのデータベースからデータを取得します。これを元に、サーバーでページを作って、Webブラウザーに渡す仕組みになっています。

実行の様子を見てみると!

リストを表示するページには、実行の様子を調べるために、「console.log()」でメッセージを表示するようにしてみました。メッセージを入れた場所は

  1. getServerSideProps()に入ってすぐ:「server」
  2. getServerSideProps()がデータを返す直前:「return」
  3. Listnのコンストラクタの中:「constructor」
  4. componentDidMount()の中:「did mount」
  5. レンダリングの最初の部分:「表示データとタイムスタンプ」

これを見ると面白い事がわかります。

実は、コンストラクタ(constructor())とレンダリング(render())は2回実行されているのがわかります。

最初は、サーバー側のコンソールで、「getServerSideProps()」が実行された後、コンストラクタとレンダリングが呼び出されているのがわかります。

Server
return
constructor
Data: [
  { id: '3ZZ93Q1Khlt8vWisWnM8', name: 'new' },
  { id: 'F6F9tVilsdOjY0DffRTV', name: 'old' },
  { id: 'dxV4CZv6UQiUuhp7MYku', name: 'good' },
  { id: 'qVaC13Z5rgN6RJkUEYrT', name: 'bad' }
] Wed Mar 31 2021 22:32:35 GMT-0700 (Pacific Daylight Time)

続いてブラウザ側では、

list.tsx?7749:10 : constructor 
list.tsx?7749:16 : Data: (4) [{…}, {…}, {…}, {…}] Wed Mar 31 2021 22:32:36 GMT-0700 (Pacific Daylight Time) 
list.tsx?7749:13 : did mount 

のメッセージが出力されています。データの部分のタイムスタンプを見ると1秒のズレがあるので、同じでない事がわかります。

つまり、サーバー側でページを作る時にも、Webブラウザー側で表示する際もコンストラクタと、レンダリングが呼び出されて実行されている事になります。

動作は複雑!

このように、実行の過程を追いかけてみると、Nextの動作はちょと複雑ということがわかるかと思います。 この例では、ページの読み込み以外は起こらない設計ですが、実際のデータの取得からサーバー側でのページの処理、Webブラウザ側の処理の一連の動作は、通常のシンプルなHTMLファイルベースのサイトや、フロントエンドのフレームワークのみの実装よりは複雑になっています。

前回のように、APIを利用する場合は、さらにページ内のイベントなどによって、サーバー側とのやりとりが増える分さらに複雑になります。これに加えて、フロントエンドで動的なレンダリングも可能なことを考えると、Nextのフレームワークの実行で起きている事は結構複雑になります。

ページのタイプも大きく3種類のページのタイプに分ける事ができます。

  • 完全に静的なページ(HTMLなど)
  • サーバー側で予め作成可能なページ(静的データに基づくページ、ビルド時に作成)
  • リクエスト時に作製するページ(今回の例のようなページ、動的に作成されます)

また、Next内の動作でも

  • ページのリクエスト
  • APIのリクエスト

など、サーバー側とWebブラウザのやり取りも大きく2つに分ける事ができます。

少し規模の大きなWebサイトやアプリの場合、これらが混在する場合が多いため、動作を考える際もタイプによってグループに分けて考えないと全体の動きを把握するのが難しくなります。

ReactとNextは別物!

以前の投稿でも触れていますが、ReactとNextは似ているように見えますが、中身は全く別物です。 NextでWebアプリやWebサービスを設計する場合、サーバー側の処理もよく理解しておく必要があります。どのコードがサーバー側で実行されて、どのコードがWebブラウザ側で動作するのかをよくわかっていないと、デバッグも難しくなりますし、セキュリティ上の問題も起きやすくなります。

オンライン決済がサーバー側の処理で行うことが多いのは決済に必要な秘密鍵(シークレットキー/トークン)を利用する場合が多いため、この情報が簡単に見られないようにするための措置です。 フロントエンドとバックエンドを完全に別々に実装する場合は、その境界がハッキリしているので問題が少ないのですが、Nextなどのフレームワークの場合、フロントエンドとバックエンドが混在しているため、よく理解していないと想定外の問題が起きやすくなります。

Nextを利用する場合は、そうした事をよく理解した上で使うことが重要です!

まとめ

ReactとNextはどちらも、仮想DOMを記述するJSXを利用していることや、Reactの機能を共用しているためとても似ている部分がたくさんあります。しかし、実際の動作を詳しく見て見ると、フロントエンドのコードとバックエンドのコードが混在しているため、慣れないと動作する場所を見極めるのが難しい一面があります。

サーバー側で実装する一つの理由がセキュリティ対策がありますが、コードがどこで動くかをきちんと把握していないとセキュリティホールを作ってしまう可能性も大きくなります。

Nextに取り組む際は、フロントエンドだけではなくバックエンドの正しい知識が重要になります。

Copyright(c) 2017-2021 by Silicon Valley Super Ware, all rights reserved.

コメント

このブログの人気の投稿

ユーザーインターフェースの設計

足し算以外もできるようにする

改良版足し算プログラム