コンテンツにスキップ
ブログに戻る

2019年9月30日月曜日

Next.js 9.0.7

投稿者

Next.js 9 は約2ヶ月前にリリースされました。それ以来、私たちは7つの小規模ながらも非常に重要なリリースを行ってきました:9.0.1, 9.0.2, 9.0.3, 9.0.4, 9.0.5, 9.0.6、そして 9.0.7 です。

これらのリリースが、ウェブサイトやアプリケーションにどのような変化をもたらしたのか、一切の破壊的変更なしにご紹介します。

Windows環境での並列処理の改善

Next.jsは、next buildプロセスの多くの場所で並列処理を行います。主な用途は、Terserによるビルド出力の並列ミニファイです。

以前は、この作業はworker-farmというパッケージを使用して多数のCPUで処理されていました。しかし、多くのWindowsユーザーがカスタムwebpack設定でミニファイを無効にしていることに気づきました。さらに調査したところ、worker-farmはWindowsマシンでは一貫して動作しないことがわかりました。

この問題を解決するために、worker-farmからjest-workerに移行しました。これにより、macOS、Linux、Windowsマシンでビルドが一貫して信頼性高く実行されるようになります。

jest-workerは、その名前が示すように、Jestテストランナーの一部です。Jestがテストケースを並列化するために使用するパッケージです。これは、このパッケージが非常に実績があり、信頼性が高く、メンテナンスされていることを意味します。

jest-workerは、Node 12の新機能であるworker_threadsもサポートしています。child_processとは異なり、worker_threadsはメモリを共有できます。これは、新しいNodeバージョンでのビルド時間の短縮を意味します。

jest-workerに切り替えることで、Windowsユーザー向けのビルド並列処理を再度有効にすることができました。

デフォルトでのGzip圧縮

企業がカスタムサーバーを使用する理由を調査したところ、最も多かったのが圧縮のためでした。企業は、HTTPレスポンスのGzip圧縮を処理するcompressionというExpressミドルウェアを追加していました。

このミドルウェアはレスポンスを圧縮するため、ユーザーに送信されるバイト数が少なくなります。通常、これはNginxのようなリバースプロキシによって処理されるべきです。リバースプロキシは、CPU負荷の高い作業をシングルスレッドのNodeプロセスからオフロードします。

しかし、ウェブ上でのNext.jsの使用状況を調査したところ、かなりの割合の企業で圧縮がまったく設定されていなかったことがわかりました。

Vercelのようなプラットフォームでは、gzipbrotliはプロキシレベルで自動的に処理されます。

セルフホスティングの場合、企業は(compressionまたはリバースプロキシ経由で)gzipを自分で追加する必要があります。

Next.js 9.0.4以降、next startまたはカスタムserver.jsを使用する際に、gzip圧縮がデフォルトで含まれるようになりました。

Node.jsがBrotliをネイティブでサポートするようになったため、brotliサポートも間もなく提供されます

アプリケーションでカスタムサーバー経由で既に圧縮が有効になっている場合、Next.jsは独自のコンプレッサーを追加しません。

Next.jsは、サーバーレスターゲットではデフォルトで圧縮を含みません。なぜなら、サーバーレスターゲットを使用する場合、アセットは別々にアップロードされ、Node.js経由で提供されないためです。

Vercelのようなプラットフォームにデプロイする場合、変更は不要です。

アクティブなページのみでのTypeScriptレポート

Next.js 9には、TypeScriptの組み込みサポートが含まれています。必要なのは、単一のページを.jsから.tsxにリネームすることだけです。Next.jsは、残りのセットアップを自動的に処理またはガイドします。

Next.jsは、開発プロセスと並行してtsc --watchを実行することで、型チェックも処理します。開発中、Next.jsにはオンデマンドエントリーという概念があります。この機能は、アクティブに作業しているページのみをコンパイルします。

The on-demand entries flow
オンデマンドエントリーフロー

9.0.4以降、Next.jsは、アクティブにコンパイルされているページのみに対して型エラーを報告するようになりました。これにより、特定のページに集中している間、型チェックのノイズが大幅に削減されます。

アプリケーション全体の型チェックは、next build中に実行されるか、エディタで処理できます。

テレメトリ

Next.jsはほぼ3年前にリリースされ、この3年間でフレームワークは大幅に成長しました。新機能から、すべてのユーザーにとってより良いデフォルト設定まで、多岐にわたります。

この改善プロセスへのアプローチは、非常に手動的なものでした。

Vercelには、いくつかの大規模なNext.jsアプリケーションがあります。例えば、 vercel.comvercel.com/docs、そしてhttps://nextjs.dokyumento.jpです。私たちはVercel社内でNext.jsをドッグフーディングし、その経験に基づいてNext.jsを改善してきました。

さらに、コミュニティと積極的に関わり、フィードバックを収集しています。あなたが、あなたの会社でNext.jsをどのように使用しているかについて、Timと話したことがあるかもしれません。

例えば、カスタムサーバーを使用しているか、カスタムwebpack設定があるかなどです。このフィードバックは、Next.jsの機能開発の方向性を定める上で非常に価値があります。

しかし、このアプローチには問題があります。それは、ユーザーのサブセットからしかフィードバックを収集できないことです。このサブセットは、あなたやあなたの会社とは異なるニーズやユースケースを持っている可能性があります。

例えば、CSSファイルのインポートは標準的ではありませんが、かなりの数のユーザーがnext-css(またはSass/Less)、あるいはカスタム設定を通じてこれを使用しているようです。その特定の方式を使用しているユーザーの割合がわかれば、それを改善する優先順位を付けることができます。

このため、私たちは、より自動化された匿名のフィードバック収集アプローチを導入しました。これにより、近い将来、Next.jsをさらに改善できるようになります。

これにより、フレームワークに加えられた改善が、すべてのアプリケーションのベースラインを向上させているかどうかを検証することも可能になります。

テレメトリの詳細については、nextjs.org/telemetryをお読みください。参加したくない場合は、オプションでオプトアウトする方法も記載されています。

ビルドフィードバック(アクティビティを示すドット付き)

Next.jsユーザーとの会話で、next buildが(主に視覚的に)何もしていないように見えることがあるという小さなフィードバックがありました。

これを解決するために、next build実行中にコンソール出力にローディングインジケーターを追加しました。この出力は、コマンドがまだ実行中であり、プロセスがフリーズしていないことを視覚的に示します。

将来的には、このビルド出力を拡張して、可能な限りビルドの多くのステージを表示する予定です。

新しいビルドインジケーターのドット

next/head要素トラッキングの改善

Next.jsは、アプリケーションのHTMLレンダリングを担当するため、<head>要素を管理するための組み込み方法を提供します。このAPIはnext/headモジュールを通じて公開されています。

例えば、ページにタイトルを追加するには

pages/index.js
import Head from 'next/head';
 
export default function MyPage() {
  return (
    <>
      <Head>
        <title>My Title</title>
      </Head>
      <h1>Hello World</h1>
    </>
  );
}

HTMLにレンダリングする際、Next.jsは<Head>内でレンダリングされたすべてのコンポーネントを収集し、ページの<head>にタグをレンダリングします。

しかし、Next.jsは<Link>コンポーネントを使用して、シングルページアプリケーション(SPA)タイプのルート遷移を可能にします。

<Link>をクリックすると、Next.jsはページをクライアントサイドでレンダリングするために必要なJavaScriptファイルをフェッチします。その後、ファイルに関連付けられたReactコンポーネントをレンダリングします。

この遷移はクライアントサイドで発生するため、前のページから注入された<head>要素をクリーンアップする必要があります。そうでなければ、古い要素が別のページに存在する可能性があります。

以前は、Next.jsは<Head>から提供されたすべての要素にクラス名を追加することで、これらの要素を追跡していました。

上記の例では、<head>は次のようになります。

<head>
  <title class="next-head">My Title</title>
</head>

このソリューションはうまく機能しました。なぜなら、next/headを通じて注入されたすべての要素は明確にマークされ、クリーンアップが容易だったからです。

しかし、少数のユーザーから、要素の追加のclass属性が、外部サービスから追加されたスクリプトを検証できなくすることがあるという問題が報告されました。

Google ChromeチームのGerald Monacoは、クラス名を要素に含める必要なく、クリーンアップ動作を維持する方法を考案しました

新しい動作は、Next.jsが(next/headが)レンダリングした要素の数を含む追加の<meta>タグを挿入することです。これにより、Next.jsはカウントを使用して既存の要素をクリーンアップできます。

結果として、このアプローチは、複数の要素がページの<head>に注入される際に、初期HTMLペイロードサイズを削減します。

pagesディレクトリでの非ページファイルの防止

Next.jsを使い始めるとき、最初にすることはpagesディレクトリを作成することです。

規約として、pagesディレクトリ内のすべてのファイルがアプリケーションのルートになります。簡単な例として、pages/about.js/aboutになります。

時間が経つにつれて、ユーザーのアプリケーション(大小問わず)のビルドパフォーマンスが低いという報告が時折寄せられました。

さらに調査したところ、これらのユーザーはpagesディレクトリにコンポーネント構造全体を作成していたことが明らかになりました。

pagesディレクトリ内のすべてのファイルはページとして扱われるため、すべてのコンポーネントがアプリケーションのページとしてコンパイルされていました。これにより、無効なページに対して2つ以上のJavaScriptファイルが生成され、ビルド時のオーバーヘッドが大きくなりました。

さらに、これはNext.jsがコード分割チャンクを生成する方法にも部分的に影響しました。なぜなら、Next.jsはページ間のライブラリ使用状況に関するヒューリスティクスを使用しているからです。

そのため、ユーザーがこの落とし穴をNext.jsアプリケーションに導入しないようにする必要があります。

Next.js 9は、pagesディレクトリ内のファイルがReactコンポーネントをエクスポートすることを検証するようになりました。

実際には、Next.jsは、pagesディレクトリで非ページファイルが見つかった可能性について警告するメッセージを表示します。

これにより、ユーザーはページではないファイルを別のディレクトリに移動するように促されます。その結果、開発、本番環境、コード分割がより高速で正確になります。

ランタイムの改善

Next.jsフレームワークは多くの部分で構成されています。その一つがクライアントサイドランタイムです。このランタイムは、ハイドレーション、クライアントサイドルーティングなどを処理します。

この改善が焦点を当てたハイドレーションは、サーバーレンダリングまたはプリレンダリングされたHTMLをインタラクティブにするために必要なものです。ハイドレーションは、イベントハンドラーを追加し、useEffect()componentDidMountのようなライフサイクルメソッドを呼び出して、アプリケーションをエンドユーザーが利用できるようにします。

さらに、Next.jsは基本的なハイドレーション以上のものを処理します。例えば、クライアントサイドルーターの設定、next/headの設定、next/dynamicを通じた追加アプリケーションロジックのロードなどです。

これらの各責任にも、それぞれ固有のランタイム部分があります。

next/dynamicの場合、Next.jsはサーバーでレンダリングされた遅延ロードコンポーネントがクライアントサイドで準備ができていることを保証する必要があります。next/dynamicのすべての使用は追加のJavaScriptバンドルを生成し、ハイドレーションミスマッチを避けるためにこれらのファイルをハイドレーション前にロードする必要があります。

以前は、このランタイムは常にNext.jsランタイムバンドルに含まれていました。現在では、アプリケーションでnext/dynamicを使用している場合にのみ含まれます。これは、next/dynamicを使用しないアプリケーションでは、ダウンロード、解析、実行されるJavaScriptが少なくなることを意味します。

AppTreeサポート

Reactエコシステムのいくつかのライブラリは、非常に特殊な方法でサーバーサイドレンダリングを実装しています。特に、ApolloのサーバーサイドレンダリングソリューションであるgetDataFromTreeは、Reactツリーをレンダリングし、見つかったすべてのQueryについて結果を待ってからReactツリーを再度レンダリングすることで機能します。

デフォルトでは、Next.jsはコンテキスト値をReactツリーに追加します。例えば、useRouterで読み取れるルーターなどです。

with-apollo例がReactツリーをレンダリングしていた方法は、<App>をレンダリングし、不足しているプロパティを手動で埋めようとすることでした。React Contextの追加により、コンテキストプロバイダーが別の要素であるため、これはもはや不可能でした。

Next.js 9.0.4以降、getInitialPropsのコンテキストオブジェクトにAppTreeという新しいプロパティが追加されました。これは、ApolloのgetDataFromTreeのように、外部ライブラリがReactツリー全体をトラバースする必要がある場合の特別なケースのために追加されました。

with-apollo例は、変更を反映するように更新されました。既にアプリにApolloを実装している場合は、将来追加されるuseRouterなどのAPIがNext.jsアプリケーションで正しく機能するように、AppTreeアプローチに更新することをお勧めします。

Apolloや類似のライブラリを使用していない場合、Next.jsチームは一般的にReactツリーのトラバースを推奨していないため、AppTreeの使用は避けることをお勧めします。Reactツリーは一度だけでなく複数回レンダリングされるため、かなりのパフォーマンスオーバーヘッドが発生します。

next-serverパッケージの削除

1年以上前にサーバーレスデプロイメントの最適化を開始した際、next-serverというパッケージを作成しました。このパッケージは実験的であり、nextパッケージと並行して公開されました。公には文書化されていませんでしたが、可能な限り最小限のNext.jsサーバーランタイムを作成するための実験でした。

最終的に、このパッケージは成功し、本番サーバーランタイムを小さくすることに成功しました。しかし、Next.jsコンパイラと静的解析で、ランタイムをさらに小さくする革新的な方法を考案しました。

その結果、next-serverは廃止され、サーバーレスターゲットに置き換えられました。このターゲットは、next-serverパッケージをnextの代替として使用するよりも、はるかに最適化された出力を持っています。

このパッケージは廃止され、直接使用することはできませんでしたが、そのままにしていました。なぜなら、パッケージ間で共有される内部コンポーネントがあり、コードの移動にはかなりの労力が必要だったためです。

最近、この労力をかけてnext-serverのコードをnextパッケージに戻しました。これは、Next.jsフレームワークのすべてのコードがnextパッケージに格納されていることを意味します。

これにより、初心者や経験豊富な貢献者の両方にとって、Next.jsへの貢献が容易になります。単一のコンパイルプロセスと統合されたビルド構成になりました。以前は、nextnext-serverの個別の設定があり、各パッケージにどのコードが属するかについて任意の上限がありました。

Next.jsのアップグレード

古いバージョンのNext.jsでプロジェクトを実行している場合は、Next.js 9へのアップグレードをお勧めします。

ほとんどの場合、アップグレードに特別な変更は必要ありません。スムーズなアップグレード体験のために、アップグレードガイドに従ってください。

ガイドの更新にご協力いただいたすべてのコミュニティ貢献者に感謝いたします。

今後の予定は?

このブログ記事で概説された新しい最適化は、私たちが取り組んできた広範な最適化と機能の始まりにすぎません。

進行中のRFCについては、まもなくアップデートを共有します。それまでは、GitHubのRFCラベルを通じて、少しのプレビューをご覧いただけます。

これは、組み込みCSSサポートpublicディレクトリサポート、そしてsrcディレクトリサポートのような、私たちが調査している機能の一部を示しています。

コミュニティ

Next.jsコミュニティの継続的な成長を楽しみにしています。

  • 800人以上の貢献者が少なくとも1つのコミットを記録しました。
  • GitHubでは、プロジェクトは41,100回以上スターを獲得しました。

Next.jsコミュニティは、前回のメジャーリリース以来、10,900人以上のメンバーに倍増しました。参加しましょう!

リリースを形成する継続的なコミュニティの貢献と外部からのフィードバックに興奮しています。