目次
- Next.jsの概要
- Next.jsを使うべき5つの理由
- Next.jsを使う上での問題点と解決法
- まとめ
-1. 宣伝
本記事を元にしたNext.jsの入門書、『基礎から学ぶ Next.js』を出版いたしました!
モダンJavaScript/TypeScriptの書き方からNext.jsの概要、さらに最新のReact 18/Next.js 13の内容までカバーしています。ハンズオンも豊富にあり、実際に手を動かしながらNext.jsでのアプリケーション開発を学べる内容になっています。
ハンズオンのコードは以下のリポジトリにあります。
0. TL;DR
Next.jsはモダンかつ強力なフロントエンドフレームワークで、様々な長所を持っています。本稿の目的はそうしたポイントをご紹介し、Next.jsの利用を勧めることです。
1章ではまずNext.jsの概要について簡単にまとめます。
2章ではNext.jsを使うべき5つの理由をご紹介します。
- SSR/SSG
- ファイルベースルーティング
- 開発サーバの部分的な高速リロード(Fast Refresh)
- 画像最適化
- ゼロコンフィグ
3章ではこうした機能を実際に使う上での問題点と、その解決方法を説明します。
4章ではそれらを踏まえて、Next.jsを使うことに強力なモチベーションが存在すると結論します。
1. Next.jsの概要
Next.jsはReactをベースにしたフロントエンドフレームワークです。Next.js関係の記事が1000LGTM付くなど本邦でも注目度向上がうかがえます。
Next.js 4年目の知見:SSRはもう古い、VercelにAPIサーバを置くな
Next.jsはサーバーサイドレンダリング(SSR)やファイルベースルーティングなど多くの機能をゼロコンフィグで提供してくれます。また、開発会社Vercelが同名のプラットフォームVercelを展開しており、デプロイ/ビルド/配信までを一気通貫に提供しています。
Reactをベースにしている以上、Next.jsを使うべきかの判断はReactとの比較がポイントとなるでしょう。以下では基本的にNext.jsがReactと比較してどういった利点を持っているのかを紹介していきます。
2. 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に有利です。
現在(10.0.3)のNext.jsはよく言われるようなSSRのフレームワークではありません。以下のようにStatic Generation(SSG)が推奨されています。
We recommend using Static Generation over Server-side Rendering for performance reasons.
Statically generated pages can be cached by CDN with no extra configuration to boost performance.
こうしたイメージがあるのは、Next.jsが当初はSSRのフレームワークとして誕生した歴史的経緯からです。ただ、実用上リクエスト時までページの(静的な)内容が確定しないことはまれです。またリクエストごとに静的リソースを生成するよりビルド時に一度だけ生成したほうがパフォーマンスが高いのは自明でしょう。こうした理由からSSG機能が追加され、現在ではそちらが主流になりました。
さて、具体的にSSR/SSGを同実装するのかを簡単に見たいと思います。方法は簡単で、各ページのルートファイル(pages/hoge.js)から
-
getStaticProps
(SSG) -
getServerSideProps
(SSR)
という関数ををexportするだけです。ビルド/リクエスト時にそれらが呼ばれ、戻り値がコンポーネントの引数に入ります。
function Blog({ posts }) {
// Render posts...
}
// This function gets called at build time
export async function getStaticProps() {
// Call an external API endpoint to get posts
const res = await fetch('https://.../posts')
const posts = await res.json()
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
}
}
export default Blog
Basic Features: Pages | Next.js
そしてSSRとSSGの切り替えはNext.jsがビルド時に自動で判断してくれます。つまり、getServerSideProps
などSSR用のメソッドがつかわれていればビルド時には静的ファイルを作成しません。反対に、それらが使われていなければビルド時に自動で静的ファイルの生成までやってくれます。
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" }
というパラメータがページに渡されます。
Routing: Dynamic Routes | Next.js
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).
また、匿名関数をデフォルトエクスポートしている場合もこの機能を使えません。
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.
なので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
Basic Features: Image Optimization | Next.js
next/image
コンポーネントのwidth/heightを指定しておくとそのサイズまであらかじめ画像を圧縮してくれます。また、レスポンス表示で幅が小さくなった場合も自動でそのサイズにトリミングした画像を生成してくれます。
2.5. ゼロコンフィグ
上記すべてについて、webpack等の設定の必要がありません。
しかしながら、実用上の細かな設定は手動で行う必要があります。その場合はnext.config.js
に各種設定を書くと自動で読み込み、webpackの設定に追加してくれます。
また、例えばlessの設定用のプラグインなどが公式からnpmで配布されています。
よく使われるものはたいていそろっており、これらを使えばほぼゼロコンフィグでかなりの部分まで設定できるようになっています。
プラグインがなく、手書きで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
で使うとライブラリのトランスパイルが実現できます。
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
を返すよう設定しています。こうしないとトップページ以外に直接アクセスした際に正しくページを読み込めません。
3.4. 細かい設定の情報をどこで集めるか
以上で見たような細かな設定について、生態系が発達していない現在では日本語の情報源はありません。Next.jsのGitHubのissueが主な情報源です(何度も助けられました)。逆に、自分が直面したような問題についてはインターネットのどこかしらですでに議論があるとも言えます。
この記事で自分が直面した問題についてはなるべく情報を残すようにしました。また本稿やNext.js Advent Calendar 2020によって少しでもユーザーが増えてくれば情報も豊かになってくるのではないかと期待しています。
4. まとめ
1章で見たように、Next.jsはReactをベースにしたフロントエンド開発に大きなメリットをもたらします。2章では実際に利用する上で直面した問題点をいくつかご紹介しました。自分の業務上ではいずれの問題も解決することができました。また今後日本語でアクセスできる情報が増えていき、より簡単に解決できるようになることも期待できます。
以上から本稿ではNext.jsの利用には強いモチベーションが存在すると結論します。筆者も今後積極的にNext.jsを使っていこうと考えています。
文献案内
-
Next.js 非公式日本語翻訳ドキュメント
有志によるドキュメントの日本語訳もあります -
Issues · vercel/next.js
困ったらissueを検索してみましょう -
Next.js 4年目の知見:SSRはもう古い、VercelにAPIサーバを置くな
Next.jsを四年使っている方の記事で、歴史的背景も詳しく参考になります -
なぜNext.jsを採用するのか? - mottox2 blog
去年のNext.js Advent Calendarの記事です。同様にNext.jsの採用メリットが書かれています