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

2019年2月11日(月)

Next.js 8

投稿者

本日、本番環境対応のNext.js 8を発表できることを誇りに思います。主な機能は以下の通りです。

常にそうであるように、これらの利点がすべて完全に後方互換性があることを保証するよう努めてきました。ほとんどのNext.jsアプリケーションでは、以下のコマンドを実行するだけで済みます。

ターミナル
npm i next@latest react@latest react-dom@latest

コミュニティの皆様、そして私たちの成功に賭けてくださったすべての方々に感謝いたします。前回のブログ投稿以来、AT&Tスターバックス、そしてTwitchといった企業がNext.jsで公開ウェブサイトやアプリを再起動するのを見てきました。

Serverless Next.js

Next.jsのサーバーレスターゲットは、ページからServerless関数を出力します

Serverlessデプロイは、アプリケーションをより小さな部分(ラムダとも呼ばれます)に分割することで、信頼性とスケーラビリティを劇的に向上させます。Next.jsの場合、pagesディレクトリ内の各ページがサーバーレスラムダになります。

サーバーレスには多くの利点があります。参照リンクではExpressの文脈でその一部が述べられていますが、原則は普遍的に適用されます。サーバーレスは、分散された障害点、無限のスケーラビリティを可能にし、「使用した分だけ支払う」モデルで信じられないほど手頃な価格です。

Next.jsでサーバーレスモードを有効にするには、next.config.jsserverlessビルドtargetを追加します

next.config.js
module.exports = {
  target: 'serverless',
};

serverlessターゲットは、ページごとに単一のラムダを出力します。このファイルは完全にスタンドアロンであり、実行に依存関係を必要としません

  • 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;
  • http.IncomingMessage
  • http.ServerResponse
  • voidは関数が戻り値を持たないことを指し、JavaScriptのundefinedと同等です。関数を呼び出すとリクエストが完了します。

Next.jsは、ホスティングプラットフォームによって関数シグネチャが異なるため、サーバーレスデプロイメント向けの低レベルAPIを提供します。一般的には、Next.jsのサーバーレスビルドの出力を互換性レイヤーでラップすることをお勧めします。

例えば、プラットフォームがNode.jsのhttp.Serverクラスをサポートしている場合

server.js
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 https://:3000'));

概要

  • サーバーレスデプロイメントを実装するための低レベルAPI
  • pagesディレクトリ内のすべてのページがサーバーレス関数(ラムダ)になります
  • 最小サイズのサーバーレス関数を作成します(ベースzipサイズ50 KB
  • 関数の高速なコールドスタートに最適化
  • サーバーレス関数は依存関係を持ちません(関数バンドルに含まれています)
  • Node.jsのhttp.IncomingMessagehttp.ServerResponseを使用
  • next.config.jstarget: 'serverless'を使用してオプトイン
  • serverターゲットは引き続き完全にサポートおよび維持されます
  • publicRuntimeConfigserverRuntimeConfigserverlessモードではサポートされていません。代わりにビルド時設定を使用してください。

ビルド時のメモリ使用量の大幅な削減

Next.js(およびwebpackエコシステムのその他すべて)のビルド性能とリソース利用率を向上させるために、webpackに貢献してきました。

この取り組みにより、性能を劣化させることなく最大16倍のメモリ使用量改善が実現しました。

メモリがはるかに迅速に解放され、多くの負荷(多数のページ)がかかってもプロセスがクラッシュすることがなくなりました。

この最適化をどのように達成したかについては、近日中に詳しく説明します。Next.jsブログにご注目ください。

ビルド時環境設定

Next.jsアプリケーションをレビューする中で、アプリケーションに設定値を提供するためにbabel-plugin-transform-defineまたはwebpack.DefinePluginを追加するというパターンが頻繁に観察されました。

Next.js 8では、後方互換性を保ちながら同じ機能を提供するために、next.config.jsenvという新しいキーを導入します

next.config.js
module.exports = {
  env: {
    customKey: 'MyValue',
  },
};

これにより、コード内でprocess.env.customKeyを使用できるようになります。例えば

pages/index.js
export default function IndexPage() {
  return <h1>The value of customKey is: {process.env.customKey}</h1>;
}

process.env.customKeyはビルド時に'MyValue'に置き換えられます。

プリフェッチ性能の向上

Next.jsルーターは、ページをプリフェッチしてより高速なナビゲーションを可能にします

pages/index.js
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後にのみプリフェッチを開始します。

さらに、Next.jsは2Gインターネットとnavigator.connection.saveDataモードを検出し、低速なネットワーク接続でのプリフェッチを無効にするようになりました。

初期HTMLサイズの縮小

Next.jsがHTMLをプリレンダリングする際、ページを<html><head><body>、およびページをレンダリングするために必要なJavaScriptファイルを含むデフォルトの構造でラップします。

Next.js 7では、初期ペイロードを1.50KBに最適化し、これは以前のバージョンから7.4%の削減でした。

さらに初期ペイロードサイズを1.16KBに削減し、さらに23%の削減を達成しました

7.08.0差分
ドキュメントサイズ(サーバーレンダリング)1.50KB1.16KB23% 縮小

サイズを削減した主な方法は以下の通りです

  • ページ初期化のインラインスクリプトが削除されました
  • /_errorページがすべてのページ読み込みに含まれなくなりました

/_errorのオンデマンド読み込み

本番環境でエラーが発生した場合、エラーが発生したことを表示するために/_errorページがレンダリングされます。

Next.jsの最初のリリース以来、/_errorページのスクリプトタグは初期HTMLの一部であり、これはランタイムエラーがない場合には使用されないにもかかわらず読み込まれていたことを意味します。

Next.js 8からは、エラーが発生した際に/_errorページがオンデマンドで読み込まれるようになりました。

これは、デフォルトで読み込まれ、パースされ、実行されるコードが少なくなったことを意味します。

DXの改善

Next.jsの主な目標の1つは、**最高の開発者体験**とともに最高のプロダクション性能を提供することです。このリリースには、ユーザーからのフィードバックに基づいた多くの細かな改善が含まれています。

オンデマンドエントリーの改善

Next.jsは、デフォルトで*積極的に*開発中のページのみを自動的にコンパイルします。next devが実行されるたびに、pagesディレクトリ内のすべてのページをコンパイルするわけではありません。代わりに、アクセスしたときにページをコンパイルします。

例えば、https://:3000/my-pageにアクセスすると、pages/my-page.jsファイルがオンデマンドでコンパイルされ、その後ページがレンダリングされます。

これにより、開発者は開発サーバー起動時にすべてのページがコンパイルされるのを待つ必要がなくなります。これは大規模なアプリではかなりの時間がかかることがあります。また、バンドル時にすべてのページを考慮する必要がないため、メモリ使用量を低く抑え、コンパイラの速度を高速に保ちます。

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

25秒間アクセスされなかったページは、コンパイラのビルドキャッシュから破棄され、コンパイラの高速化とメモリ使用量の削減に貢献します。

Next.jsがページへのアクセスを追跡する方法は、ポーリングメカニズムを使用することです。5秒ごとに「on-demand-entries-ping」が送信され、Next.js開発サーバーに特定のページがアクセスされていることを通知します。

この機能の初期リリース以来、pingはwindow.fetch呼び出しを使用して行われていたため、pingがトリガーされるたびにブラウザの開発ツールのconsolenetworkタブに表示されていました。

最も要望の多かった機能の1つは、これらのリクエストが不要なノイズを追加する可能性があるため、ブラウザ開発者ツールから非表示にする機能です。

Next.js 8では、fetchベースのpingがWebSocketベースのアプローチに置き換えられたことを発表できることを嬉しく思います。これにより、pingは引き続き行われますが、WebSocket接続を検査するときにのみ表示されます。

WebSocketへの変換に協力してくれたJJ Kasperに感謝します。

開発中のポートリスニングの高速化

Next.js開発サーバーを起動すると、リクエストを処理できるように初期コンパイルを実行する必要があります。デフォルトでは、Next.jsはこのコンパイルステップが完了するまでHTTPサーバーの起動を待っていました。つまり、next devを実行してからブラウザにアクセスした場合、HTTPサーバーがまだ接続をリッスンしていないために「このサイトにアクセスできません」というメッセージが表示されることがありました。

Next.js 8では、コンパイルが開始される前にHTTPが接続をリッスンするようになります。つまり、コンパイルが完了する前にhttps://:3000/にアクセスしても、ページが利用可能になるまで更新し続ける必要がなく、初期コンパイルが完了するまでリクエストが待機して処理されます。

この機能を実装してくれたBrian Beckに感謝します。

静的エクスポートの高速化

Next.jsは、高いパフォーマンスを達成するための手段として、**プリレンダリング**という考え方に焦点を当てています。プリレンダリングには2つの形式があります

  • サーバーレンダリングは、各リクエストがレンダリングをトリガーするものです。その結果、エンドユーザーはデータを消費し始めるためにJavaScriptがダウンロードされるのを待つ必要がありません。
  • 静的レンダリングは、サーバー上でコードを実行することなく直接提供できる静的ファイルを出力するものです。

Next.js 8から、マシンに複数のCPUがある場合、next exportによる静的レンダリングの速度が向上します。

4CPUコアのMacBookでのテストによると、全コアを活用してページをプリレンダリングすることで、エクスポート速度が1秒あたり約25ページから75ページに向上しました。

Next.jsはCPUコア数を自動的に検出し、それに応じてページを分散するため、コードの変更は不要です。

この機能を実装してくれたBenjamin Knifflerに感謝します。

Head要素の重複排除

アプリケーションを構築する際によくあるニーズは、ページの<head>要素を更新することです。例えば、レスポンシブデザインのために<title><meta name="viewport">を設定するなどです。

Next.jsは、<head>に変更を加えるための組み込みコンポーネントを公開しています。

pages/index.js
import Head from 'next/head';
 
export default function IndexPage() {
  return (
    <>
      <Head>
        <title>My page title</title>
      </Head>
    </>
  );
}

<Head>コンポーネントは、異なるコンポーネントで複数回使用することもできます。例えば、レイアウトコンポーネントでデフォルトのheadタグを設定できます。

ただし、デフォルトのheadタグを異なる値で上書きしたい場合があります。Next.jsの以前のバージョンでは、タグを重複排除する方法がなかったため、出力でタグが重複してしまっていました。

このため、<Head>コンポーネント内のすべての要素にkeyプロパティを提供できるようになり、同じkey値を持つタグが自動的に重複排除されます。

2つのタグにkey="viewport"を設定した場合、最後のタグのみがレンダリングされます。

pages/index.js
import Head from 'next/head';
export default function IndexPage() {
  return (
    <>
      <Head>
        <title>My page title</title>
        <meta
          name="viewport"
          content="initial-scale=1.0, width=device-width"
          key="viewport"
        />
      </Head>
      <Head>
        <meta
          name="viewport"
          content="initial-scale=1.2, width=device-width"
          key="viewport"
        />
      </Head>
    </>
  );
}

セキュリティの改善

新しいcrossOrigin設定オプション

Next.js 6では、pages/_document.js<Head><NextScript>crossOrigin属性を追加するオプションを導入しましたが、これはcross-originを設定するすべてのユースケースをカバーしていませんでした。

Next.jsには、<script>タグを動的に挿入するクライアントサイドルーターがありますが、これらのタグは挿入時にcross-origin属性が欠落していました。

すべての<script>タグにcross-originが設定されるようにするため、next.config.jsに新しい設定オプションを導入しました

next.config.js
module.exports = {
  crossOrigin: 'anonymous',
};

このオプションを導入するもう一つの利点は、アプリケーションでcross-originを設定するためにカスタムのpages/_document.jsが不要になったことです。

以前の動作は引き続きサポートされますが、開発中に警告が出力され、開発者が新しく導入されたオプションに移行するのを支援します。

インラインJavaScriptの削除

Next.js 7以前を使用する場合、Content Security Policy (CSP)を有効にするには、ユーザーはポリシーにscript-src 'unsafe-inline'を含める必要がありました。これは、Next.jsがデータを渡すため、例えばgetInitialPropsの結果をクライアントサイドに渡すためにインラインの<script>タグを作成していたためです。

Next.js 8では、このインラインスクリプトタグをクライアントへの安全な転送のためにJSONタグに変更しました。これにより、Next.jsによってインラインスクリプトが含まれることはなくなりました。

慎重な検討により、script-src 'self'が使用できるようになりました。

API認証の例

Next.jsで、あらゆるプログラミング言語の外部APIに対して認証を行う方法が、これまでで最も要望の多かった例の1つでした。

Next.js 8の導入に伴い、新しく作成された例であるwith-cookie-authも導入します。

この例は、外部のNode.js APIに対して認証を行う方法を示していますが、適用される概念はあらゆるステートレスAPIに適用されます。

この例では、サーバーサイドとクライアントサイドのレンダリング間でトークンを共有するためにクッキーを使用しています。

そうすることで、アプリがサーバーでレンダリングされた場合でも、ユーザーに代わって認証済みデータをフェッチできます。

この例に貢献してくれたJuan Olveraに感謝します。

コミュニティ

Next.jsは最初のリリース以来、Fortune 500企業から個人のブログまで、あらゆる場所で使用されてきました。Next.jsの採用が継続的に成長していることを大変嬉しく思います。

  • 600人以上の貢献者が少なくとも1つのコミットを行ってきました。
  • GitHubでは、このプロジェクトは34,400回以上スターされています。
  • 最初のリリース以来、2600件以上のプルリクエストが提出されました。

Next.jsコミュニティには4,570人以上のメンバーがいます。ご参加ください!