ブログに戻る — 2019-02-11T08:00:35.000Z
投稿者: Connor Davis (@connordav_is), Shu Ding (@shuding_), Tim Neutkens (@timneutkens)
Next.js 8
本日、プロダクション対応の Next.js 8 を発表します。主な新機能と改善点は次のとおりです。
- Serverless Next.js
- ビルド時メモリ使用量の大幅な削減
- ビルド時の環境設定(build-time environment configuration)
- Prefetch のパフォーマンス改善
- 初期 HTML サイズの縮小
- on-demand entries の改善
- 開発時のポート待ち時間の短縮
- Static Export の高速化
- Head 要素の重複除去(deduplication)
- 新しい
crossOrigin 設定オプション
- インライン JavaScript の削除
- API 認証の例
常に通り、これらの改善は完全な後方互換性を確保するよう努めています。ほとんどの Next.js アプリケーションでは、以下を実行するだけで十分です。
npm i next@latest react@latest react-dom@latest
コミュニティと、Next.js を採用してくださった皆様に感謝します。前回のブログ以降、AT&T、Starbucks、Twitch のような企業が公開ウェブサイトやアプリを Next.js で再構築しました。
Serverless Next.js
Next.js の serverless ターゲットは、pages の各ページを Serverless 関数(いわゆる lambda)として出力します。これによりアプリケーションを小さな単位に分割でき、信頼性とスケーラビリティが大幅に向上します。サーバーレスの利点としては、障害点の分散、事実上の無限スケーラビリティ、使った分だけ支払うというコスト効率の良さなどがあります。
Next.js で serverless モードを有効にするには、next.config.js に serverless のビルドターゲットを追加します。
module.exports = {
target: 'serverless',
}
serverless ターゲットはページごとに単一の lambda を出力します。このファイルは完全にスタンドアロンで、実行に外部依存を必要としません。
pages/index.js => .next/serverless/pages/index.js
pages/about.js => .next/serverless/pages/about.js
Next.js の Serverless 関数のシグネチャは、Node.js の HTTP サーバーコールバックに似ています:
type Function = (req: http.IncomingMessage, res: http.ServerResponse) => void;
ここで void は関数が値を返さないことを表し、JavaScript の undefined と同等です。関数を呼び出すことでリクエストは終了します。
ホスティングプラットフォームごとに関数のシグネチャが異なるため、Next.js は serverless デプロイ向けの低レベル API を用意しています。一般に、Next.js の serverless 出力を互換レイヤーでラップすることになります。例えばプラットフォームが Node.js の http.Server クラスをサポートしている場合:
const http = require('http');
const page = require('./.next/serverless/about.js');
const server = new http.Server((req, res) => page.render(req, res));
server.listen(3000, () => console.log('Listening on http://localhost:3000'));
サマリー
- serverless デプロイを実装するための低レベル API を提供
- pages ディレクトリ内の各ページが serverless 関数(lambda)になる
- 可能な限り最小の serverless 関数を生成(ベースの zip サイズは 50 KB)
- コールドスタートの高速化に最適化
- serverless 関数は依存関係が 0(依存は関数バンドルに含まれる)
- Node.js の
http.IncomingMessage と http.ServerResponse を使用
next.config.js に target: 'serverless' でオプトイン
注意: server ターゲットは引き続き完全にサポートおよびメンテナンスされます。publicRuntimeConfig と serverRuntimeConfig は serverless モードではサポートされません。代わりにビルド時設定を使用してください。
ビルド時メモリ使用量の大幅な削減
webpack へのコントリビュートにより、Next.js(および webpack エコシステム全体)のビルドパフォーマンスとリソース利用が改善されました。その結果、パフォーマンスの低下なく最大で 16 倍に相当するメモリ使用量の改善が得られています。メモリはより迅速に解放され、多数のページを扱うストレス下でもプロセスのクラッシュが発生しにくくなりました。これらの最適化の詳細については、今後ブログで深堀りしていきます。
ビルド時の環境設定(env)
多くの Next.js アプリケーションで babel-plugin-transform-define や webpack.DefinePlugin を使って設定値を注入するパターンが見られました。Next.js 8 では後方互換性を保ちながら、next.config.js に新しいキー env を導入して同等の機能を提供します:
module.exports = {
env: {
customKey: 'MyValue',
},
};
この設定によりコード内で process.env.customKey を使用でき、ビルド時に process.env.customKey が 'MyValue' に置き換えられます。例:
export default function IndexPage() {
return <h1>The value of customKey is: {process.env.customKey}</h1>;
}
(ビルド時に process.env.customKey が 'MyValue' に置換されます。)
Prefetch のパフォーマンス改善
Next.js のルーターはナビゲーションを高速化するためにページを prefetch できます。例:
import Link from 'next/link';
export default function IndexPage() {
return (
<>
<Link href="/about" prefetch>
<a>To About Page</a>
</Link>
</>
);
}
これは prefetch 属性があるリンクの JavaScript バンドルを事前取得することで動作します。Next.js 8 以前はこれを実現するためにドキュメントの <body> に <script> タグを挿入していましたが、この方法はページを開いた際にオーバーヘッドを生み、ブラウザの「読み込み中」表示が実際より長く出ることがありました。
Next.js 8 では、prefetch に <script> の代わりに <link rel="preload"> を使用し、さらに onload 後にのみプリフェッチを開始してブラウザにリソース管理を任せます。加えて、2G と判定される接続や navigator.connection.saveData モードではプリフェッチを無効化して遅いネットワークでの無駄な通信を避けます。
初期 HTML サイズの縮小
Next.js は HTML をプリレンダリングする際、デフォルトで <html>, <head>, <body> とページをラップし、ページをレンダリングするための JavaScript を追加します。Next.js 7 では初期ペイロードを約 1.50KB に最適化しましたが、Next.js 8 では更に改善し、1.16KB(さらに 23% の削減)を達成しました。
- Document サイズ(サーバーレンダリング): 1.50KB -> 1.16KB(23% 小型化)
主な削減方法は次のとおりです。
- ページ初期化用のインラインスクリプトを削除
/_error ページを全ページで初期ロードしないように(オンデマンド読み込み)
/_error のオンデマンド読み込み
本番環境でエラーが発生した際には /_error ページがレンダリングされます。これまでは /_error のスクリプトタグが初期 HTML に含まれていたため、ランタイムエラーがない場合でもロードされていました。Next.js 8 からは /_error をエラー発生時にオンデマンドで読み込むようにしたため、デフォルトで読み込まれるコード量を減らすことができます。
開発体験(DX)の改善
Next.js の主な目標の一つは「最高のプロダクション性能を最高の開発体験と両立すること」です。このリリースではユーザーからのフィードバックに基づく多くの細かな改善を含んでいます。
Improved on-demand entries
Next.js は開発モードでアクティブに開発中のページだけを自動的にコンパイルします。next dev 起動時に pages ディレクトリ内の全ページを毎回コンパイルするわけではなく、ページへアクセスしたときにオンデマンドでコンパイルされます。例えば http://localhost:3000/my-page を訪問すると pages/my-page.js がオンデマンドでコンパイルされ、その後ページがレンダリングされます。
これにより、開発サーバー起動時に全ページのコンパイルを待つ必要がなく、大規模アプリでも起動が速く、メモリ使用量を低く保てます。
on-demand entries のフロー
- ページにアクセスがない状態が 25 秒続くと、そのページはコンパイラのビルドキャッシュから破棄され、コンパイラを高速に保ちメモリ使用を削減します。
- ページがアクセスされていることの追跡にはポーリング機構を使います。5 秒ごとに "on-demand-entries-ping" が送信され、開発サーバーにそのページがアクセス中であることを通知します。
- 初期実装では ping を
window.fetch で行っていたため、ブラウザの開発者ツールのコンソールやネットワークにノイズとして表示されていました。Next.js 8 ではこれを WebSocket ベースに置き換え、ping は引き続き発生するものの、WebSocket 接続を検査した場合にのみ確認できるようになりました。
(JJ Kasper に感謝)
開発時のポート待ち時間の短縮
従来は開発サーバー起動時に初回コンパイルが完了するまで HTTP サーバーのリスニングを開始せず、next dev 実行後すぐにブラウザで http://localhost:3000/ にアクセスすると「このサイトに接続できません」的な表示が出ることがありました。Next.js 8 ではコンパイル開始前に HTTP が先に接続を受け付けるようになり、初回コンパイルが完了するまでリクエストは待機してから応答されるため、ページが利用可能になるまでリロードし続ける必要がなくなります。
(Brian Beck に感謝)
Static Export の高速化
Next.js は高性能を実現する手段としてプリレンダリングを重視しています。プリレンダリングには以下の 2 形態があります。
- サーバー側レンダリング(リクエストごとにレンダリング)
- 静的レンダリング(
next export による静的ファイル出力)
Next.js 8 から next export による静的レンダリングは、マシンに複数 CPU がある場合に高速化されます。例えば 4 コアの MacBook では、エクスポート速度が約 25 ページ/秒から 75 ページ/秒に向上しました。Next.js は自動的に CPU コア数を検出してページを分配するため、コード変更は不要です。
(Benjamin Kniffler に感謝)
Head 要素の重複除去
アプリケーションを構築する際に <head> を更新する必要はよくあります(例: <title> や <meta name="viewport">)。Next.js はこれを行うための組み込みコンポーネントを提供しています。
import Head from 'next/head';
export default function IndexPage() {
return (
<>
<Head>
<title>My page title</title>
</Head>
</>
);
}
<Head> コンポーネントは複数のコンポーネントで何度でも使用できます(例えばレイアウトコンポーネントでデフォルトの head タグを設定しつつ、個別ページで上書きする等)。従来の Next.js では上書きしようとするとタグが重複して出力されることがありましたが、Next.js 8 では <Head> 内の要素に key プロパティを付与することで同一キーを持つタグを自動的に重複除去(deduplicate)できます。たとえば key="viewport" を 2 つのタグに付けた場合は最後に定義した方のみがレンダリングされます。
簡単な例:
import Head from 'next/head';
export default function IndexPage() {
return (
<>
<Head>
<meta key="viewport" name="viewport" content="width=device-width,initial-scale=1" />
<title key="title">My page title</title>
</Head>
</>
);
}
以上が Next.js 8 の主な変更点と改善点です。詳細や今後の深掘り記事は Next.js ブログをチェックしてください。