Back to Blog
Wednesday, September 19th 2018
投稿者: Giuseppe Gurgone (@giuseppegurgone), Shu Ding (@shuding_), Tim Neutkens (@timneutkens)
26回の canary リリースと 340 万ダウンロードを経て、本番対応の Next.js 7 を発表できることを誇りに思います。本リリースの主な内容:
- DX 改善:起動が最大 57% 高速化、再コンパイルが 42% 高速化
- react-error-overlay によるエラー表示の改善
- コンパイルパイプラインのアップグレード:Webpack 4 と Babel 7
- 標準化された Dynamic Imports
- Static CDN サポート
- 小さくなった初期 HTML ペイロード
- App と Pages 間の SSR を含む React Context
さらに、今回の発表は新しくなった Nextjs.org でも共有できることを嬉しく思います。
DX(開発者体験)改善
Next.js の主要な目標の一つは、最高の本番性能と最高の開発者体験を提供することです。本リリースはビルドおよびデバッグパイプラインに多くの重要な改善をもたらします。
コンパイル速度
webpack 4、Babel 7、そしてコードベースへの多数の最適化により、Next.js は開発時の起動が最大 57% 速くなりました。新しいインクリメンタルコンパイルキャッシュにより、コード変更のビルドが 40% 程度速くなります。以下は収集した例です:
- 起動時間(基本アプリ): 6.0 = 1947ms → 7.0 = 835ms(57% 高速化)
- コード変更(基本アプリ): 6.0 = 304ms → 7.0 = 178ms(42% 高速化)
おまけとして、開発およびビルド時に webpackbar によるより良いリアルタイムフィードバックが得られます。
React Error Overlay によるエラー表示の改善
正確で助けになるエラー表示は、優れた開発/デバッグ体験に不可欠です。これまではエラーメッセージとスタックトレースをレンダリングしていましたが、今後は react-error-overlay を利用してスタックトレースを強化します。強化点は以下の通りです:
- サーバーエラーとクライアントエラーの両方に対する正確なエラー位置
- コンテキストを示すソースのハイライト
- リッチなスタックトレース全体の表示
これにより、エラーオーバーレイの改善(以前のものと react-error-overlay の比較)が可能になり、特定のコードブロックをクリックするだけでテキストエディタを開くことも簡単になります。
Webpack 4
Next.js は最初のリリース以来 webpack によってバンドルされており、多くのプラグインや拡張を再利用してきました。Next.js は最新の webpack 4 を採用し、さまざまな改善とバグフィックスが含まれます。主な利点:
- .mjs ソースファイルのサポート
- コード分割の改善
- より良いツリーシェイキング(未使用コードの削除)
- WebAssembly サポート(Next.js は WebAssembly をサーバーサイドレンダリングすることも可能です。例はここにあります)
注: このアップグレードは後方互換です。ただし next.config.js を通してカスタム webpack ローダーやプラグインを使っている場合は、それらをアップデートする必要があるかもしれません。
CSS インポート
webpack 4 では mini-extract-css-plugin と呼ばれる新しい CSS 抽出方法が導入されました。@zeit/next-css、@zeit/next-less、@zeit/next-sass、@zeit/next-stylus は現在 mini-extract-css-plugin を利用しています。これらの Next.js プラグインの新バージョンは CSS インポートに関する既存の 20 件の問題を解決します。たとえば、動的 import() 内での CSS インポートがサポートされるようになりました:
components/my-dynamic-component.js
import './my-dynamic-component.css';
export default function MyDynamicComponent() {
return <h1>My dynamic component</h1>;
}
pages/index.js
import dynamic from 'next/dynamic';
const MyDynamicComponent = dynamic(
import('../components/my-dynamic-component')
);
export default function Index() {
return (
<div>
<MyDynamicComponent/>
</div>
);
}
大きな改善点として、もう pages/_document.js に次のような記述を追加する必要はありません:
<link rel="stylesheet" href="/_next/static/style.css" />
Next.js が自動的に CSS ファイルを注入します。プロダクションでは Next.js が CSS URL にコンテンツハッシュを自動的に追加するため、変更があっても古いバージョンがユーザーに配信されることを防ぎ、不変の恒久的キャッシュを導入できます。
要するに、Next.js プロジェクトで .css ファイルのインポートをサポートするには、next.config.js で withCSS プラグインを登録するだけです:
const withCSS = require('@zeit/next-css')
module.exports = withCSS({ /* my next config */ })
標準化された Dynamic Imports
Next.js は Version 3 から next/dynamic を通じて動的インポートをサポートしてきました。早期採用者として import() を扱う独自の実装をしていたため、後に webpack が導入した動作と一部乖離し、いくつかの機能が欠けていました。例えば、手動で特定ファイルを同じチャンク名にまとめることができませんでした:
import( '../lib/my-library');
また、next/dynamic でラップされていない import() の使用も難しかったです。Next.js 7 からはデフォルトの import() 挙動に手を加えなくなり、import() のフルサポートがそのまま利用可能になりました。この変更も後方互換です。
動的コンポーネントの利用は従来通り簡単です:
pages/index.js
import dynamic from 'next/dynamic';
const MyComponent = dynamic(
import('../components/my-component')
);
export default function Index() {
return (
<div>
<MyComponent />
</div>
);
}
この例では my-component 用の新しい JavaScript ファイルが作成され、<MyComponent /> がレンダリングされたときのみ読み込まれます。重要なのは、レンダリングされない場合、その <script> タグは初期 HTML ペイロードに含まれないことです。
コードベースを簡素化し、React エコシステムを活用するために、Next.js 7 では next/dynamic を内部で react-loadable を使うように書き換えました(一部小さな変更あり)。これにより、次の新機能が追加されます:
- next/dynamic の timeout オプションによるタイムアウト
- next/dynamic の delay オプションによるローディングコンポーネントの遅延表示(インポートが非常に速い場合にローディング状態をスキップするため)
Babel 7
Next.js 6 はベータ版だった頃の Babel 7 を導入しました。その後 Babel 7 が安定版としてリリースされ、Next.js 7 はこの安定版を利用します。変更点の完全な一覧は Babel のリリースノートを参照してください。主な機能の一部:
- Typescript サポート(Next.js では @zeit/next-typescript を利用可能)
- Fragment 構文 <> のサポート
- babel.config.js のサポート
- overrides プロパティで特定ファイル/ディレクトリにのみプリセット/プラグインを適用可能
Next.js プロジェクトにカスタム Babel 設定がなければ破壊的変更はありません。もしカスタム Babel 設定がある場合は、カスタムプラグイン/プリセットを最新バージョンにアップグレードする必要があります。Next.js 6 未満からのアップグレードの場合は babel-upgrade ツールが有用です。
さらに、Next.js の Babel プリセット (next/babel) は、NODE_ENV が test に設定されているときに modules オプションをデフォルトで commonjs に設定するようになりました。この設定が .babelrc を作成する主な理由になっていることが多かったため、従来は次のような .babelrc を使用していました:
.babelrc
{
"env": {
"development": { "presets": ["next/babel"] },
"production": { "presets": ["next/babel"] },
"test": { "presets": [["next/babel", { "preset-env": { "modules": "commonjs" } }]] }
}
}
Next.js 7 では以下で十分です:
.babelrc
{
"presets": ["next/babel"]
}
この時点で .babelrc を削除しても、Babel 設定がない場合 Next.js が自動的に next/babel を使用します。
小さくなった初期 HTML ペイロード
Next.js は HTML を事前レンダリングする際、ページをデフォルトの構造(<html>, <head>, <body>)とページレンダリングに必要な JavaScript ファイルでラップします。以前の初期ペイロードは約 1.62 kB でした。Next.js 7 では初期 HTML ペイロードを最適化し、現在は 1.50 kB(7.4% の削減)になりました。
- Document サイズ(サーバーレンダリング): 6.0 = 1.62kb → 7.0 = 1.50kb(7.4% 小さくなった)
削減の主な方法:
- __next-error div の削除
- インラインスクリプトの縮小(将来のリリースで完全に削除予定)
- 使用されていない NEXT_DATA プロパティ(例: nextExport, assetPrefix)をコンパイル時に削除
Static CDN サポート
Next.js 5 で assetPrefix を導入し、資産を CDN など特定の場所から自動で読み込む方法を提供しました。通常の CDN(プロキシ型)では以下のような URL をリクエストします:
https://cdn.example.com/_next/static/<buildid>/pages/index.js
この場合 CDN はキャッシュにファイルがあれば返し、なければオリジンから取得します(これはエッジネットワークが行うプロキシングです)。しかし一部の CDN ソリューションでは .next ディレクトリを事前に直接アップロードする必要があります。問題は Next.js の URL 構造が .next フォルダ内のフォルダ構造と一致していなかったことです。たとえば以前は:
https://cdn.example.com/_next/static/<buildid>/pages/index.js
// マップされる先: .next/page/index.js
Next.js 7 では .next のディレクトリ構造を URL 構造に合わせるよう変更しました:
https://cdn.example.com/_next/static/<buildid>/pages/index.js
// マップされる先: .next/static/<buildid>/pages/index.js
プロキシ型 CDN の使用を推奨しますが、この新しい構造により別タイプの CDN を使って .next ディレクトリを直接アップロードするユーザーも対応可能になります。
styled-jsx v3
デフォルトで含まれる Next.js の CSS-in-JS ソリューションである styled-jsx の v3 を導入します。styled-jsx 3 は React Suspense に対応できる準備が整っています。
子コンポーネントが現在のコンポーネントスコープに含まれていない場合(たとえば親コンポーネント内でのみ適用したいスタイルが子に必要な場合)、どのようにスタイルを当てるかが分かりにくいことがありました。例:
pages/index.js
const ChildComponent = () => (
<div>
<p>some text</p>
</div>
);
export default function Index() {
return (
<div>
<ChildComponent />
<style jsx>{` p { color: black; } `}</style>
</div>
);
}
上のコードは p タグを選択しようとしますが動作しません。styled-jsx のスタイルは現在のコンポーネントにスコープされ、子コンポーネントには漏れないためです。回避策として :global を使う方法がありましたが、これはページ全体にスタイルが漏れるという別の問題を引き起こします。
styled-jsx 3 では css.resolve という新しい API を導入してこの問題を解決しました。これにより、指定した styled-jsx 文字列に対する className と <style> タグ(styles プロパティ)を生成できます:
pages/index.js
import css from 'styled-jsx/css';
const ChildComponent = ({ className }) => (
<div>
<p className={className}>some text</p>
</div>
);
const { className, styles } = css.resolve` p { color: black; } `;
export default function Index() {
return (
<div>
<ChildComponent className={className} />
{styles}
</div>
);
}
この新 API により、子コンポーネントへ透過的にカスタムスタイルを渡せるようになります。
styled-jsx のメジャーリリースのため、styled-jsx/css を使っている場合にはバンドルサイズを改善する破壊的変更が一つあります。styled-jsx 2 では外部スタイルの "scoped" と "global" バージョンを生成していましたが、scoped だけが使われている場合でも global バージョンを含めていました。styled-jsx 3 では global スタイルは css ではなく css.global でタグ付けする必要があります。
(原文はこの先が途中で切れているため、ここまでが翻訳の対象です。)