Next.jsについて調べる

Next.jsを使っているのだが、内部的にどう動いているのかよくわかっていないので軽く調べてみた。

以下を使って、SSR処理時やルーティングでの挙動を見てみる。 https://github.com/zeit/next.js/tree/fc05c9c27307fd6910f7d87b8c65b085751ce190/examples/parameterized-routing

以下のような流れでサーバ起動まで実行できる。

$ npx create-next-app --example parameterized-routing parameterized-routing-app
$ cd parameterized-routing-app
$ npm run build
$ npm start

pagesディレクトリは以下のようなファイル構成になっている。

pages
├── blog.js
└── index.js

0 directories, 2 files

ビルド時に生成される .next は以下のようなディレクトリになっていた。

.next
├── BUILD_ID
├── build-manifest.json
├── bundles
│   └── pages
│       ├── _app.js
│       ├── _error.js
│       ├── blog.js
│       └── index.js
├── server
│   ├── bundles
│   │   └── pages
│   │       ├── _app.js
│   │       ├── _document.js
│   │       ├── _error.js
│   │       ├── blog.js
│   │       └── index.js
│   └── pages-manifest.json
└── static
    └── commons
        └── main-ee347f50f148624764ad.js

7 directories, 13 files

.next/bundles, .next/server/bundles の各ディレクトリに、pagesディレクトリのファイルに対応する blog.js, index.js が生成され、 _app.js, _error.js, _document.js も生成されている (_document.js はserverのみ)

_app.js, _error.js, _document.js は各ページのテンプレートのようなものであり、ソースコード上でカスタマイズもできる。今回は特にカスタマイズしていなかったが、出力としては個別のファイルに出たのだろう。 クライアント用のコードは圧縮されており、pages配下のディレクトリを、クライアント用とサーバ用でそれぞれ生成しているのだということが予想できる。(_document.js はサーバ側でしか実行されないため、サーバ用のコードしか吐かれていないのだろう)

npm start してから curl localhost:3000 すると、以下のようなHTMLが取れた。

<!DOCTYPE html><html><head><meta charSet="utf-8" class="next-head"/><link rel="preload" href="/_next/8f79b495-de7d-47be-8e34-55823460818f/page/index.js" as="script"/><link rel="preload" href="/_next/8f79b495-de7d-47be-8e34-55823460818f/page/_app.js" as="script"/><link rel="preload" href="/_next/8f79b495-de7d-47be-8e34-55823460818f/page/_error.js" as="script"/><link rel="preload" href="/_next/static/commons/main-ee347f50f148624764ad.js" as="script"/></head><body><div id="__next"><ul><li><a href="/blog/first">My first blog post</a></li><li><a href="/blog/second">My second blog post</a></li><li><a href="/blog/last">My last blog post</a></li></ul></div><div id="__next-error"></div><script>
          __NEXT_DATA__ = {"props":{"pageProps":{}},"page":"/","pathname":"/","query":{},"buildId":"8f79b495-de7d-47be-8e34-55823460818f","assetPrefix":"","nextExport":false,"err":null,"chunks":[]}
          module={}
          __NEXT_LOADED_PAGES__ = []
          __NEXT_LOADED_CHUNKS__ = []

          __NEXT_REGISTER_PAGE = function (route, fn) {
            __NEXT_LOADED_PAGES__.push({ route: route, fn: fn })
          }

          __NEXT_REGISTER_CHUNK = function (chunkName, fn) {
            __NEXT_LOADED_CHUNKS__.push({ chunkName: chunkName, fn: fn })
          }

          false
        </script><script async="" id="__NEXT_PAGE__/" src="/_next/8f79b495-de7d-47be-8e34-55823460818f/page/index.js"></script><script async="" id="__NEXT_PAGE__/_app" src="/_next/8f79b495-de7d-47be-8e34-55823460818f/page/_app.js"></script><script async="" id="__NEXT_PAGE__/_error" src="/_next/8f79b495-de7d-47be-8e34-55823460818f/page/_error.js"></script><script src="/_next/static/commons/main-ee347f50f148624764ad.js" async=""></script></body></html>%                                                  [

グローバル変数を用いて、必要なデータをObjectとして渡しているのがわかる。 他の処理はhistory関連の処理だろうか?

気になるのは _next/8f79b495-... みたいなファイルをダウンロードしようとしているところだ。 .nextディレクトリと一対一で対応していない。 8f79b495-... の値は .next/BUILD_ID と同じもののようだ。これを見てNext.jsが動的にルーティングしているのだろうか。 また、各jsファイルはbundleディレクトリ配下のものと同じものっぽい。

$ cat .next/bundles/pages/_app.js | md5
9f6ae7fa86f03007f1ffd9b31e5581d6
$ curl http://localhost:3000/_next/8f79b495-de7d-47be-8e34-55823460818f/page/_app.js | md5
9f6ae7fa86f03007f1ffd9b31e5581d6

以上の処理を見ると、レンダリング結果のHTMLを返しつつも、クライアント側に必要なjsファイルは非同期でダウンロードしているようだ。 また、別のページとなる blog.js はダウンロードしていないということにも注意したい。

次は、ブラウザからlocalhost:3000にアクセスし、画面遷移を行なってみる。

リンクをクリックして画面遷移すると、 blog.js だけが新たにダウンロードされて画面遷移が行われた。 ここはSPAの挙動となり、スムーズな画面遷移が行えるようになっている。