Next.jsの特徴 – Reactとの比較

Next.jsとはVercel社が作ったReactのフレームワークです。単体のReactより機能が拡張されています。Reactの特徴は全て引き継がれ、コードの書き方もReactそのものですので、Reactユーザーであれば難なく使うことができます。本稿ではNext.jsの概要について書きたいと思います。

Next.jsの特徴

ReactにはないNext.jsの代表的な特徴として次のようなものがあります。

  1. バックエンドとフロントエンドが同じサーバで動く
  2. Static GenerationがデフォルトでSSRも使える(クライアント側の軽量化)
  3. React Routerを使わずとも、Router機能が使える
  4. <Head>コンポーネントが使える
  5. Reactよりセットアップがずっと早い
  6. その他Next.js固有の機能が使える

バックエンドとフロントエンドが同じサーバで動く

フルスタックのWebアプリを作ろうとすると、フロントエンドサーバーとは別にバックエンドサーバーを立てる必要がありますが、Next.jsでは一つのサーバで両者が動いています。具体的には、Next.jsのプロジェクトを立ち上げると、pagesフォルダ内にapiというサブフォルダがあり、その中のファイルは全てバックエンドとなります。初期設定ではhello.jsというファイルがあります。バックエンドはNext.jsに固有の記述ですが、Expressも動かすことができます。

Static Generation(SG)とServer-side Rendering(SSR)

SG又はSSRの実装がない外部APIを伴うサイトをユーザーが訪れると、ユーザー側(クライアント側)で外部API等からのデータをリクエストしながらレンダリングする必要があるので、読み込みと表示に時間差が発生します。下記をご覧ください。ユーザーからのリクエスト①を受けたフロントエンドサーバーは、②HTML, CSS, JavaScriptを返して役目を終え、ユーザーの側では③フロントエンドサーバーから受けた内容だけで表示できるものがまず表示されます。③と同時に外部APIにリクエスト④を出し、レスポンス⑤を経てやっと⑥に至り外部APIのデータがユーザーに表示されます。この③と⑥の時間差が表示の時間差の原因です。JavaScriptで作ったサイト又はReactで構築したサイトは常にこの挙動です。SG, SSRに対してClient-side Rendering(CSR)と呼ばれます。

外部APIとの通信に時間を要するのは仕方がないものの、であるのならば時間差なしすべての情報が揃ってからまとめて表示される方がユーザー側としては快適です。また、この時間差はSEOの観点からもマイナスです。Googleのロボットがホームページを訪れたときは外部APIからリクエストを待たずに、初回表示の内容を重視すると言われます。

この時間差問題を解決するのがSGとSSRです。いずれも初回読み込み時に必要なデータをサーバー側で揃えてから、ユーザーには完成された最小限のHTML、CSS、JavaScriptだけを返します。次のようなイメージです。⑤の部分で時間差が生じません。

初回読み込み後は、SSRではユーザー(クライアント)からのリクエストの都度、サーバー側で毎回レンダリングをしてクライアントに返します。他方SGではリクエストの都度、サーバーでレンダリングをせず、クライアントはCDNのキャッシュを使います。Next.jsの公式サイトのこちらのページではSGを推奨しており、具体例含めて大変参考になります。

Router機能が内蔵

ReactはSingle Page Applicationに最適化されており、複数のページを備えようとすると以前の私のこちらの記事で書いたようにReact Routerというライブラリーを使う必要があります。Next.jsでは、Reactの仮想DOM機能そのままに、Reactよりもずっと簡単に複数のページを持つWebアプリ(サイト)を作ることができます。

具体的にはNext.jsのプロジェクトを立ち上げるとpagesというフォルダがあるので、その中にReactのコンポーネントファイルを置くだけでページが作れます。ファイル名がそのままパスになりますので、先頭の文字はReactのコンポーネントのように大文字ではなく小文字で始めます。

上の例では、login.jsというファイルを作成していますので、localhost:3000/loginというパスで、login.jsの内容が表示されます。階層を深くするにはpagesの中にサブフォルダを設置、その中にReactのコンポーネントを設置します。上記の例ではchatというフォルダがありますので、localhost:3000/login/chat/…と続きます。

通常のReactのコンポーネントも使用可能です。この例ではcomponentsフォルダに、Reactのコンポーネントを幾つか格納しています。pages内のファイルと異なり、こちらは純粋なReactコンポーネントですので、ファイル名の先頭は大文字にします。両者の使い分けは、後ほどユーザー認証の実装の部分でも簡単に解説します。

サイト内のページ遷移にはReact Routerと同様の<Link>というコンポーネントが使用できます。<Link>は仮想DOMと差分レンダリングで動くので、<a>タグを使うよりずっと高速です。

<Head>コンポーネントが使える

Reactアプリケーション内に複数ページを持たせるのみならず、SEOも考慮してそれぞれのページに固有のヘッダー(メタ情報)を持たせたいことがあります。ReactのRouterでは、ページ毎に固有のヘッダーを持たせることが難しいのですが、Next.jsではJSX内に<Head>コンポーネントを設置することで容易にヘッダーを設置できます。

具体的には次のコードをご覧ください。1行目でHeadをインポートし、<Head></Head>コンポーネントの内側でページのタイトルをAboutとしています。なおここでは<Head>以外の実際の表示部分を全て別途作成のAboutContentコンポーネントから読み込んでいます。

abut.js

import Head from “next/head”;
import AboutContent from “..components/AboutContent”;

function About() {
return (
< div>
<Head>
<title>About</title>
</Head>
<AboutContent />
</ div>
);
}

export default About;

Reactよりセットアップがずっと早い

Next.jsは多機能のようで、バージョンアップするたびにファイルサイズが軽量化されていて、Reactよりも軽いとすら言えます。例えば、Reactのnpx create-react-appによるプロジェクトの立ち上げには毎回それなりの時間を要しますが、Next.jsのnpx create-next-appを使うとずっと早く立ち上がります。

その他Next.jsに固有の機能

ReactにないNext.jsに固有の機能は幾つかありますが、Image OptimizationとInternationalizationがとくに強みです。いずれも2020年のVer.10から新しく導入された機能です。

近年あらゆるWebサイトには画像が非常に多く使われ、サイトをReactなどで高速化したとしても画像の読み込みに時間を要し、画像がサイト高速化のボトルネックとなっています。Next.jsではHTMLの<img>タグではなく、<Image>コンポーネントを使うことでとくに何も考えずとも画像が最適化されます。<Image>コンポーネントはインポートが必要ですが、JSX内の使い方は<img>タグと全く同じです。

Internationalizationとは複数言語サイトのための機能です。Next.jsでは簡単にユーザーの使用する言語に応じて該当する言語のページを表示させることができます。各言語ページのパスの設定も容易です。

_app.jsの役割

バックエンドとサーバサイドレンダリングが不要な場合でも、_app.jsの使い方さえ覚えればReactの代替としてNext.jsを使いつつそのメリット(上記③~⑥)を享受することができます。Next.jsのプロジェクトを立ち上げると、_app.jsというファイルとindex.jsというファイルがあります。

index.jsにはルートパス(”/”)に表示される内容を記述します。_app.jsはそれ自体表示されませんが、ルートパスを読み込む前の処理を記述できます。ルートパス表示前の処理ですので、ユーザーの状態によってはルートパス以外のページを始めに表示させることもできます(ログイン画面など)。Reactのapp.jsの役割が、Next.jsでは_app.jsとindex.jsに二分割されたと考えるとわかりやすいです。

次のセクションでは、以前こちらの記事でご紹介したuseAuthStateの認証機能をNext.jsの_app.js上で実装する方法をご紹介します。

Next.js + firebase + useAuthStateで認証機能を実装

下記のコードをご覧ください。1行目のglobal.cssはグローバルに適用されるcssファイルでNext.jsのプロジェクトを立ち上げると自動で作成されます。useAuthStateとauthの詳細は上記以前の記事をご覧頂ければと思いますが、Reduxのようなグローバル変数(実際にはユーザーのログイン状況)を格納するためのライブラリーと捉えて頂ければここでは十分です。LoginとLoadingの部分は別途用意したものを読み込んでいます。Loginは前述Router機能が内蔵の部分でも紹介した通り、_app.jsと同じ階層にありますので固有のページを割り当てていますが、ロード中にだけに示すLoadingは固有のパスを割り当てる必要がないので通常のReactのコンポーネントにしています。なお、Next.jsではReactを明示的にインポートする必要がありません。

関数コンポーネントの内側ですが、useAuthState関数でユーザーのログイン状況がuserに格納されます。

!userが真の場合、すなわちユーザーがログインしていない場合は<Login >コンポーネントを表示します。また、loadingが真の場合、すなわちloading中の場合は<Loading />コンポーネントを表示します。ユーザーがログインし、かつloadingが偽の場合には、最下段<Component />の部分が返されます。Componentが返されると、ルートパスに設置されるindex.jsが読み込まれる仕組みです。



_app.js

import “../styles/global.css”;
import { useAuthState } from “react-firebase-hooks/auth”;
import { auth } from “../firebase”;
import Login from “./login”;
import Loading from “../components/Loading”;

function MyApp({ Component, pageProps }) {
const [user, loading, error] = useAuthState(auth);

if (loading) return <Loading />

if (!user) return <Login />

return < Component {…pageProps} >
}

export default MyApp;

_app.js自体は表示を持てないので、このように条件に応じてコンポーネントをreturnをすることが_app.jsの主な役目です。その他、全ページ(コンポーネント)に共通の読み込み時前処理としてUseStateをここに記述することができます。

今日も最後まで読んで頂きありがとうございました。