Next.jsとは?

Next.jsは比較的歴史の浅いフレームワークですが、Reactよりも開発がスムーズに行えてユーザ体験(UX)の向上にも役立つため、近年フロントエンド開発の現場で重宝されています。

Next.jsはサーバーサイドレンダリング(SSR)やファイルベースルーティングなど多くの機能をゼロコンフィグで提供してくれます。また、開発会社Vercelが同名のプラットフォームVercelを展開しており、デプロイ/ビルド/配信までを一気通貫に提供しています。

Reactをベースにしている以上、Next.jsを使うべきかの判断はReactとの比較がポイントとなります。以下では基本的にNext.jsがReactと比較してどういった利点を持っているのかを紹介していきます。

1)Next.jsを使うべき5つの理由

  • 2.1.SSR/SSG
  • 2.2.ファイルベースルーティング
  • 2.3.開発サーバの部分的な高速リロード(Fast Refresh)
  • 2.4.画像最適化
  • 2.5.ゼロコンフィグ

2.1. SSR/SSG

Reactはシングルページアプリケーション(SPA)として単一の巨大なJavaScriptを生成します。それに対して、Next.jsはアプリケーションを事前にページ単位でレンダリングします。クライアントからのリクエスト時にレンダリングするのがSSR(Server Side Rendering)と呼ばれる機能です。またビルド時にレンダリングする機能もあり、SSG(Static Site Generation)と呼ばれます。これらの機能により、各ページ読み込み時のダウンロードファイルサイズを削減できます。またURLごとに個別のHTMLが生成されるのでSEOに有利です。

2.2. ファイルベースルーティング

ReactはSPAとしての性質上、疑似的にURLを書き換えて見かけ上のページ遷移を実現しています。表示内容はjsによって書き換えられます。その際にreact-router-domライブラリを使用しますが、URLとコンポーネントの対応付けなどがやや面倒です。

Next.jsはpages/ディレクトリに置いたフォルダ/ファイルの構成に従って、HTMLを生成してページ遷移を実現します。ルーティングライブラリは不要で、URLの構造に合わせてjs(ts)ファイルを配置するだけです。

以下の例ではindex.tsx/hoge.tsx/hogeに対応付けられます。リクエストされたURLに対応するページがなければ404.tsxの内容が表示されます。

--pages
    |--index.tsx -> /
    |--hoge.tsx -> /hoge
    |--fuga.tsx -> /fuga
    |--404.tsx
    |--_app.tsx -> アプリケーションエントリーポイント
    |--_document.tsx -> HTMLドキュメント構造記述用

また、_app.tsx_document.tsxは特殊なファイルで、URLには紐づけられません。これらはアプリケーションのエントリーポイントとしてReactのContext Providerを記述したり、HTMLのヘッダーを記述するために使います。

さらにNext.jsは動的ルーティングにも対応しています。例えばpages/post/[pid]/[comment].jsに対して/post/abc/a-commentというURLでアクセスすると

{ "pid": "abc", "comment": "a-comment" }

というパラメータがページに渡されます。

2.3. 開発サーバの部分的な高速リロード(Fast Refresh)

React(webpack)の開発サーバは変更を検知してページ全体をリロードします。Next.jsの開発サーバはソースコードの変更を検知して、stateを保持したまま変更があった個所だけを更新してくれます。これにより、開発体験が大幅に向上します。例えば、フォームに入力した内容を保持したままタイトルのfont-sizeを変更することなどができます。

この機能は関数コンポーネントとReact Hooksでのみ利用可能です。(Classコンポーネントではstateを保持してリロードできないようです)

Local state is not preserved for class components (only function components and Hooks preserve state).

Basic Features: Fast Refresh | Next.js

また、匿名関数をデフォルトエクスポートしている場合もこの機能を使えません。

Anonymous arrow functions like export default () => <div />; cause Fast Refresh to not preserve local component state. For large codebases you can use our name-default-component codemod.

Basic Features: Fast Refresh | Next.js

なのでNext.jsでコンポーネントを書く際は常に関数コンポーネントに名前を付けてデフォルトエクスポートするようにしましょう。

// Fast Refreshできない
export default () => <div />;

// Fast Refreshできる
const Index = () => <div/>;
export default Index;

2.4. 画像最適化

Next.js 10.0.0から専用の画像コンポーネントが追加され、配置されるサイズに応じて元画像をトリミングして配信してくれるようになりました。必要なサイズのデータだけをダウンロードするので画像の表示を大幅に高速化できます。

import Image from 'next/image'

function Home() {
return (
    <>
        <h1>My Homepage</h1>
        <Image
            src="/me.png"
            alt="Picture of the author"
            width={500}
            height={500}
        />
        <p>Welcome to my homepage!</p>
    </>
    )
}

export default Home

next/imageコンポーネントのwidth/heightを指定しておくとそのサイズまであらかじめ画像を圧縮してくれます。また、レスポンス表示で幅が小さくなった場合も自動でそのサイズにトリミングした画像を生成してくれます。

2.5. ゼロコンフィグ

上記すべてについて、webpack等の設定の必要がありません。

しかしながら、実用上の細かな設定は手動で行う必要があります。その場合はnext.config.jsに各種設定を書くと自動で読み込み、webpackの設定に追加してくれます。

また、例えばlessの設定用のプラグインなどが公式からnpmで配布されています。

vercel/next-plugins

よく使われるものはたいていそろっており、これらを使えばほぼゼロコンフィグでかなりの部分まで設定できるようになっています。

プラグインがなく、手書きでwebpackの設定を追加していくとしてもwebpackの設定ファイルを直接書き換えるよりはるかに楽です。Reactで細かいwebpackの設定をする際にnpm ejectしてwebpack.config.jsを編集することになりますが、かなり難しい作業です。

例えばsvgを読み込む設定なら以下のように書きます。

// next.config.js
module.exports = {
    webpack(config, options) {
        config.module.rules.push(
            {
                test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
                use: [
                    {
                        loader: 'babel-loader',
                    },
                    {
                        loader: '@svgr/webpack',
                        options: {
                            babel: false,
                            icon: true,
                        },
                    },
                ],
            },
        );
    };
};

webpack.config.jsを編集する場合と比べて、ユーザーが編集した部分だけが設定ファイルに残ることになります。ライブラリ(Next.js)が設定している部分は依然隠蔽されているので、可読性が高まります。

3. Next.jsを使う上での問題点と解決法

  • 3.1. 外部ライブラリの利用
  • 3.2. styles flickering
  • 3.3. Vercel以外へのデプロイメント
  • 3.4. 細かい設定の情報をどこで集めるか

3.1. 外部ライブラリの利用

ときおり普通のReactで動くライブラリがNext.jsでビルドすると動かなかったりします。

こうした場合は、動的インポート(Dynamic Import)という機能で対処できる場合があります。動的インポートしたコンポーネントはクライアントサイドでレンダリングされるため、実質的にReactと同じように処理されるためです。

Advanced Features: Dynamic Import | Next.js

またデザインライブラリが動かない場合もあります。弊社ではよくAnt Designを利用していますが、Next.jsで使うにはかなりややこしい設定が必要でした。以下のissueが参考になります。

with-ant-design-less can’t work together with cssModule #8156

こちらのコメントの内容をそのままコピペで使えばOKです。

Ant Designについては、ライブラリの一部がECMASrciptで書かれている場合があります。そうした場合にライブラリ自体をトランスパイルしてから使う必要があります。以下のプラグインをnext.config.jsで使うとライブラリのトランスパイルが実現できます。

next-transpile-modules – npm

3.2. styles flickering

CSSモジュールなどを使いながら実装していると、以下のissueに出ている画像のように一瞬だけスタイルシートの当たっていない状態で画面が表示されてしまうことがありました。

styles flickering in dev with tailwind + css modules · Issue #12448 · vercel/next.js

issue内にあるように、ページに空のscriptタグを追加するとこの現象が発生しなくなります。

According to https://stackoverflow.com/a/42969608/943337
i just added an empty script tag and my problem of a sidebar which was popping up at first load, is now solved.

https://github.com/vercel/next.js/issues/12448#issuecomment-634711525

これを実現するためには、pagesディレクトリに_document.tsx(js)という各ページのドキュメント構造を記述するファイルをおいて以下のように書きます。

import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
    render() {
        return (
            <Html lang="ja">
                <Head>
                </Head>
                <body>
                    <Main />
                    <NextScript />
                    {/* ここに空のscriptタグを入れる */}
                    <script> </script> 
                </body>
            </Html>
        );
    }
}

export default MyDocument;

3.3. デプロイメント

Next.jsで作成したアプリケーションをデプロイするベストプラクティスはVercelを使うことです。GitHubと連携してプッシュするとビルド、デプロイまで自動化してくれます。またブランチごとに環境とURLを作ってくれる機能もあります。

しかしながら、もろもろの事情により今回はVercelを使わずにデプロイする必要がある場合があります。その際の選択肢は二つです。

  • Next.jsサーバへリバースプロキシ(next start)
  • 完全な静的リソースを生成してwebサーバから配信(next export)

Next.jsは最終的にビルドした後next startコマンドでサーバを立ち上げます。第一の方法はそのサーバにNginxなどのwebサーバからリバースプロキシすることです。

もう一つの方法として、SSRを全く使わないならnext exportコマンドでReactと同じように静的リソースを生成することもできます。あとはそれをwebサーバに返してもらうだけです。こちらの方がシンプルですが、以下のようにwebサーバの設定にひと工夫必要です。

# nginx.conf

location / {
    index index.html;
    try_files $uri $uri/ /$uri.html;
}

next exportで各ページのhtmlファイルが生成され、webサーバにはそれらを返してもらいたいです。ここで/hogeというURLでのリクエスト時に/hoge.htmlを返すよう設定しています。こうしないとトップページ以外に直接アクセスした際に正しくページを読み込めません。

文献案内

サンプル

Pro Sidebar template

See the Pen Pro Sidebar template by Mohamed Azouaoui (@azouaoui-med) on CodePen.

Hero Banner with Floating Navigation

See the Pen Hero Banner with Floating Navigation by Yudiz Solutions Limited (@yudizsolutions) on CodePen.

Responsive Navigation Menu

See the Pen Responsive Navigation Menu by Syahrizal (@syrizaldev) on CodePen.

MoIlmi.now.sh (Under Contruction)

See the Pen MoIlmi.now.sh (Under Contruction) by Alva Izun Ilmi (@izun9) on CodePen.

Image Next/Prev in JavaScript

See the Pen Image Next/Prev in JavaScript by Justin Geeslin (@justingeeslin) on CodePen.