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

2020年3月9日月曜日

Next.js 9.3

投稿者

本日、Next.js 9.3を発表できることを大変嬉しく思います。

これらの利点はすべて非破壊的で、完全に後方互換性があります。更新するには、以下のコマンドを実行するだけです。

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

次世代の静的サイト生成 (SSG) サポート

ウェブサイトやウェブアプリケーションを構築する際には、一般的に2つの戦略から選択する必要があります:静的生成 (SSG) またはサーバーサイドレンダリング (SSR)。

Next.jsは、ページごとにユースケースに最適な技術を選択できる、初のハイブリッドフレームワークです。

Next.js 9.0では、自動静的最適化の概念が導入されました。getInitialPropsのようなブロックするデータ取得要件がないページは、ビルド時に自動的にHTMLにレンダリングされます。

データ取得要件がブロックされる場合でも、ビルド時にページを静的HTMLにレンダリングしたいケースが他にもあります。例えば、(ヘッドレス)コンテンツ管理システム (CMS) やサイトのブログセクションによって提供されるマーケティングページなどです。

データ取得と静的生成を行う新しい統一された方法を作成するために、HashiCorpのようなSSGとnext exportのヘビーユーザーと協力し、Next.jsの歴史上最もコメントされたRFCでコミュニティと広範な議論を行いました。

本日、私たちは2つの新しいデータ取得メソッド、getStaticPropsgetServerSidePropsを発表できることを大変嬉しく思います。また、動的ルートの静的ページを静的に生成するためのパラメータを提供する方法、getStaticPathsも含まれています。

これらの新しいメソッドは、SSGとSSRのどちらになるのかが明確に区別されるため、getInitialPropsモデルよりも多くの利点があります。

  • getStaticProps (静的生成): データを**ビルド時**に取得します。

  • getStaticPaths (静的生成): データに基づいてプリレンダリングする動的ルートを指定します。

  • getServerSideProps (サーバーサイドレンダリング): データを**各リクエスト時**に取得します。

  • これらの改善はAPIの追加です。すべての新機能は完全に後方互換性があり、段階的に導入できます。非推奨の機能は導入されておらず、getInitialPropsは現在と同じように機能し続けます。新しいページやプロジェクトではこれらの新しいメソッドの採用を推奨します。

getStaticProps

ページからgetStaticPropsというasync関数をエクスポートすると、Next.jsはこのページをビルド時にプリレンダリングします。これは、CMSから特定の静的ページをレンダリングしたい場合に特に便利です。

getStaticPropsは常にNode.jsコンテキストで実行され、そのコードはブラウザバンドルから自動的にツリーシェイクされるため、ブラウザに送信されるコードが少なくなります。これにより、Node.jsとブラウザ環境の両方でデータ取得コードの実行について心配する必要がなくなります。これらにはいくつか矛盾があります。

これにより、fetch、REST、GraphQL、またはデータベースへの直接アクセスなど、任意の非同期または同期データ取得技術を使用できます。

pages/posts/[id].js
export async function getStaticProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  };
}

contextパラメータは、以下のキーを含むオブジェクトです。

  • params: paramsには、動的ルートを使用するページのルートパラメータが含まれます。例えば、ページ名が[id].jsの場合、params{ id: ... }のようになります。詳細については、動的ルーティングのドキュメントを参照してください。これは後で説明するgetStaticPathsと一緒に使用する必要があります。

以下は、getStaticPropsを使用してCMSからブログ投稿のリストを取得する例です。

pages/blog.js
// You can use any data fetching library
import fetch from 'node-fetch';
 
// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  );
}
 
// This function gets called at build time in the Node.js environment.
// It won't be called on client-side, so you can even do
// direct database queries. See the "Technical details" section.
export async function getStaticProps() {
  // Call an external API endpoint to get posts.
  const res = await fetch('https://.../posts');
  const posts = await res.json();
 
  // By returning { props: posts }, the Blog component
  // will receive `posts` as a prop at build time
  return {
    props: {
      posts,
    },
  };
}
 
export default Blog;

getStaticPropsはいつ使用すべきですか?

getStaticPropsは以下の状況で使用してください。

  • ページをレンダリングするために必要なデータが、ユーザーのリクエスト前にビルド時に利用可能である。
  • データがヘッドレスCMSから来ている。
  • データが公開キャッシュ可能である(ユーザー固有ではない)。
  • ページがプリレンダリングされる必要があり(SEOのため)、非常に高速である — getStaticPropsはHTMLとJSONファイルを生成し、どちらもCDNによってパフォーマンス向上のためにキャッシュされることができる。

getStaticPropsの詳細については、データ取得のドキュメントを参照してください。

getStaticPaths

ページに動的ルートがあり、getStaticPropsを使用している場合、ビルド時にHTMLにレンダリングする必要があるパスのリストを定義する必要があります。

動的ルートを使用するページからgetStaticPathsというasync関数をエクスポートすると、Next.jsはgetStaticPathsで指定されたすべてのパスを静的にプリレンダリングします。

pages/posts/[id].js
export async function getStaticPaths() {
  return {
    paths: [
      { params: { ... } } // See the "paths" section below
    ],
    fallback: true or false // See the "fallback" section below
  };
}

pathsキー (必須)

pathsキーは、どのパスがプリレンダリングされるかを決定します。例えば、pages/posts/[id].jsという名前の動的ルートを使用するページがあるとします。このページからgetStaticPathsをエクスポートし、パスとして以下を返すと、

return {
  paths: [
    { params: { id: 1 } },
    { params: { id: 2 } }
  ],
  fallback: ...
}

Next.jsは、ビルド時にpages/posts/[id].jsのページコンポーネントを使用して、posts/1posts/2を静的に生成します。

paramsの値は、ページ名で使用されているパラメータと一致する必要があります。

  • ページ名がpages/posts/[postId]/[commentId]の場合、paramsにはpostIdcommentIdが含まれる必要があります。
  • ページ名がキャッチオールルート(例:pages/[...slug])を使用する場合、paramsには配列であるslugが含まれる必要があります。例えば、この配列が['foo', 'bar']の場合、Next.jsは/foo/barでページを静的に生成します。

fallbackキー (必須)

getStaticPathsによって返されるオブジェクトには、ブール型のfallbackキーが含まれている必要があります。

フォールバック: false

fallbackfalseの場合、getStaticPathsによって返されないパスはすべて**404ページ**になります。これは、ビルド時にすべてのパスが既知であることが分かっている場合に便利です。

以下は、pages/posts/[id].jsというページごとに1つのブログ投稿をプリレンダリングする例です。ブログ投稿のリストはCMSから取得され、getStaticPathsによって返されます。その後、各ページについて、getStaticPropsを使用してCMSから投稿データを取得します。

pages/posts/[id].js
import fetch from 'node-fetch';
 
function Post({ post }) {
  // Render post...
}
 
// This function gets called at build time
export async function getStaticPaths() {
  // Call an external API endpoint to get posts
  const res = await fetch('https://.../posts');
  const posts = await res.json();
 
  // Get the paths we want to pre-render based on posts
  const paths = posts.map((post) => `/posts/${post.id}`);
 
  // We'll pre-render only these paths at build time.
  // { fallback: false } means other routes should 404.
  return { paths, fallback: false };
}
 
// This also gets called at build time
export async function getStaticProps({ params }) {
  // params contains the post `id`.
  // If the route is like /posts/1, then params.id is 1
  const res = await fetch(`https://.../posts/${params.id}`);
  const post = await res.json();
 
  // Pass post data to the page via props
  return { props: { post } };
}
 
export default Post;

フォールバック: true

fallbacktrueの場合、getStaticPropsの動作が変更され、Next.jsは提供されたパスをビルド時にHTMLにレンダリングします。ビルド時に生成されなかったパスは、ユーザーがページをリクエストしたときにオンデマンドで生成されます。

これは、アプリケーションに静的に生成できる多数のルートがあるが、ビルド時にサブセットのみを生成することでページのビルド時間の増加を避けたい場合に便利です。

ページの生成をトリガーするユーザーには、フォールバックHTMLが提供されます。これは通常、読み込み状態のページです。その理由は、静的HTMLはCDNから提供できるため、ページがまだ生成されていない場合でも常に高速であることを保証できるためです。

追加のページをオンデマンドで静的に生成する例

pages/posts/[id].js
import { useRouter } from 'next/router';
import fetch from 'node-fetch';
 
function Post({ post }) {
  const router = useRouter();
 
  // If the page is not yet generated, this will be displayed
  // initially until getStaticProps() finishes running
  if (router.isFallback) {
    return <div>Loading...</div>;
  }
 
  // Render post...
}
 
// This function gets called at build time
export async function getStaticPaths() {
  return {
    // Only `/posts/1` and `/posts/2` are generated at build time
    paths: [{ params: { id: 1 } }, { params: { id: 2 } }],
    // Enable statically generating additional pages
    // For example: `/posts/3`
    fallback: true,
  };
}
 
// This also gets called at build time
export async function getStaticProps({ params }) {
  // params contains the post `id`.
  // If the route is like /posts/1, then params.id is 1
  const res = await fetch(`https://.../posts/${params.id}`);
  const post = await res.json();
 
  // Pass post data to the page via props
  return { props: { post } };
}
 
export default Post;

getStaticPathsの詳細については、データ取得のドキュメントを参照してください。

getServerSideProps

ページからgetServerSidePropsというasync関数をエクスポートすると、Next.jsは各リクエストでこのページをレンダリングします (SSR)。

getServerSidePropsは常にサーバーサイドで実行され、コードはブラウザバンドルから自動的にツリーシェイクされるため、ブラウザに送信されるコードが少なくなります。これにより、サーバーとブラウザの両方の環境でデータ取得コードの実行について心配する必要がなくなります。これらにはいくつか矛盾があります。これにより、サーバーがデータソースへの接続が一般的に高速であるため、多くの場合でパフォーマンスが向上します。また、データ取得ロジックの露出が減ることでセキュリティも向上します。

これにより、fetch、REST、GraphQL、またはデータベースへの直接アクセスなど、任意の非同期または同期データ取得技術を使用できます。

next/linkを使用してページ間を移動する場合、Next.jsはブラウザでgetServerSidePropsを実行する代わりに、サーバーにフェッチを行い、getServerSideProps呼び出しの結果を返します。

pages/index.js
export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  };
}

contextパラメータは、以下のキーを含むオブジェクトです。

以下は、getServerSidePropsを使用してリクエスト時にデータを取得し、それをレンダリングする例です。

pages/index.js
function Page({ data }) {
  // Render data...
}
 
// This gets called on every request
export async function getServerSideProps() {
  // Fetch data from external API
  const res = await fetch(`https://.../data`);
  const data = await res.json();
 
  // Pass data to the page via props
  return { props: { data } };
}
 
export default Page;

getServerSidePropsの詳細については、データ取得のドキュメントを参照してください。

プレビューモード

この投稿で前述したように、静的生成は、ページがヘッドレスCMSからデータを取得する場合に便利です。しかし、ヘッドレスCMSでドラフトを作成し、そのドラフトをすぐにページでプレビューしたい場合には理想的ではありません。出力が静的であるため、静的ページを再生成する必要があるため、変更のプレビューが難しくなります。

Next.jsにgetStaticPropsが導入されたことで、特定の条件下でNext.jsのオンデマンドレンダリング機能を活用するなど、新しい可能性が開かれました。

たとえば、ヘッドレスCMSからのドラフトをプレビューする場合、静的レンダリングをバイパスし、公開されたコンテンツではなくドラフトコンテンツでページをオンデマンドでレンダリングしたいでしょう。Next.jsに静的生成をこの特定のケースでのみバイパスさせたいのです。

このニーズに対応するためのNext.jsの新しい組み込み機能である「プレビューモード」を発表できることを嬉しく思います。

プレビューモードを使用すると、静的に生成されたページをバイパスし、例えばCMSからドラフトページをオンデマンドでレンダリング (SSR) できます。

しかし、特定のCMSシステムに限定されるわけではありません。プレビューモードはgetStaticPropsgetServerSidePropsの両方と直接統合されているため、あらゆる種類のデータ取得ソリューションで使用できます。

プレビューモードは、next startを使用している場合、またはデプロイしてVercel Edge Networkにシームレスにデプロイすることで、すでに利用可能です。

https://next-preview.vercel.app/でプレビューモードを試してみてください。

プレビューモードの詳細については、ドキュメントを参照してください。

CMSプロバイダーとの協力

getStaticPropsを使用すると、CMSシステムを含むあらゆるデータソースからデータを取得できます。

Next.jsとの統合に関する例やガイドを提供するために、CMSエコシステムの主要なプレーヤーの多くと積極的に協力しています。

現在積極的に取り組んでいる例には、以下が含まれます。

あなたの会社がCMSエコシステムで活動している場合、ぜひご協力させてください!私たちのチームにメールまたはTwitterまでお気軽にご連絡ください。

グローバルスタイルシートの組み込みSassサポート

Next.js 9.2では、より最適化された結果を提供するために、next-cssプラグインを置き換えるグローバルCSSスタイルシートの組み込みサポートが導入されました。

リリース直後から、多くの企業がNext.jsに移行する際にSassベースの既存のデザインシステムを持っているため、Sassサポートの統合を求める声がますます多く寄せられました。

Next.jsのプラグイン使用状況を調査したところ、現在Next.jsアプリケーションの約30%がnext-sassを使用していることが判明しました。これは、バニラCSSを使用する44%、Lessを使用する6%と比較してです。

さらに、next-sassにはnext-cssと同じ制約が欠けていました。つまり、プロジェクトのすべてのファイルでSassファイルをインポートできましたが、このインポートされたSassファイルはアプリケーション全体でグローバルになっていました。

これらの統計とフィードバックを考慮し、Next.jsがSassスタイルシートのインポートを組み込みでサポートするようになったことを発表できることを大変喜ばしく思います。

アプリケーションでグローバルSassインポートを開始するには、sassをインストールします。

ターミナル
npm install sass

次に、pages/_app.js内でSassファイルをインポートします。

例えば、プロジェクトのルートにあるstyles.scssというスタイルシートを考えてみましょう。

$primary-color: #333;
 
body {
  padding: 20px 20px 60px;
  margin: 0;
  color: $primary-color;
}

まだ存在しない場合は、pages/_app.jsファイルを作成します。次に、styles.scssファイルをインポートします。

pages/_app.js
import '../styles.scss';
 
// This default export is required in a new `pages/_app.js` file.
export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

スタイルシートは本質的にグローバルであるため、カスタム<App>コンポーネントでインポートする必要があります。これは、グローバルスタイルにおけるクラス名と順序の衝突を避けるために必要です。

開発環境では、この方法でスタイルシートを記述すると、編集時にスタイルがページで自動的に更新されます。

本番環境では、すべてのSassおよびCSSファイルが自動的に結合され、単一の圧縮された.cssファイルになります。このCSSファイルは<link>タグを介してロードされ、Next.jsが生成するデフォルトのHTMLマークアップに自動的に挿入されます。

この新機能は完全に後方互換性があります。@zeit/next-sassまたは他のCSS関連のプラグインを使用している場合、競合を避けるためにこの機能は無効になります。

現在@zeit/next-sassを使用している場合は、アップグレード時にnext.config.jspackage.jsonからプラグインを削除し、組み込みのSassサポートに移行することをお勧めします。

コンポーネントレベルスタイルの組み込みSass CSSモジュールサポート

Next.jsは、[name].module.scssというファイル命名規則を使用してSassファイルと連携するCSS Modulesをサポートするようになりました。

以前Next.js 5以降で利用可能だったnext-sassを使用したサポートとは異なり、グローバルSassとCSSモジュールは**共存**できるようになりました — next-sassはアプリケーション内のすべての.scssファイルをグローバルまたはローカルとして扱う必要があり、両方を扱うことはできませんでした。

CSSモジュールは、一意のクラス名を自動的に作成することで、Sassをローカルスコープ化します。これにより、異なるファイルで同じSassクラス名を使用しても、衝突を心配する必要がありません。

この動作により、CSSモジュールはコンポーネントレベルのSassを含めるのに理想的な方法になります。CSSモジュールファイルは**アプリケーション内のどこにでもインポートできます**。

アプリケーションでSass CSSモジュールを使い始めるには、sassがインストールされていることを確認してください。

ターミナル
npm install sass

次に、components/フォルダ内の再利用可能なButtonコンポーネントを考えてみましょう。

まず、以下の内容でcomponents/Button.module.scssを作成します。

/*
You do not need to worry about .error {} colliding with any other `.css` or
`.module.css` files!
*/
$color: white;
 
.error {
  color: $color;
  background-color: red;
}

次に、components/Button.jsを作成し、上記のCSSファイルをインポートして使用します。

components/Button.js
import styles from './Button.module.scss';
 
export function Button() {
  return (
    <button
      type="button"
      // Note how the "error" class is accessed as a property on the imported
      // `styles` object.
      className={styles.error}
    >
      Destroy
    </button>
  );
}

SassファイルのCSSモジュールは**オプション**機能であり、.module.scss拡張子を持つファイルに対してのみ有効になります。通常の<link>スタイルシートおよびグローバルSassスタイルは引き続きサポートされています。

本番環境では、すべてのCSSモジュールファイルが自動的に結合され、**多数の圧縮されコード分割された.cssファイル**になります。これらの.cssファイルはアプリケーションのホット実行パスを表し、アプリケーションが描画するためにページごとに最小限のCSSがロードされることを保証します。

上記と同様に、この新機能は完全に後方互換性があります。@zeit/next-sassまたは他のCSS関連のプラグインを使用している場合、競合を避けるためにこの機能は無効になります。

現在@zeit/next-sassを使用している場合は、next.config.jsおよびpackage.jsonからプラグインを削除し、組み込みのSassサポートに移行することをお勧めします。

404の自動静的最適化

Next.js 9のリリースでは、ページにブロックするデータ要件がない場合、Next.jsがビルド時にページを静的HTMLとして自動生成する自動静的最適化の概念が導入されました。しかし、自動的に静的HTMLとしてレンダリングされなかったページが1つありました。それは404ページです。404ページが自動的に静的にされなかった主な理由は、404を駆動する/_errorページが404だけでなく、例えばエラーなども処理していたためです。

存在しないルートに対して404ページがレンダリングされることを考えると、オンデマンドでページをレンダリングすると、コストとサーバー負荷が増加する可能性があります。

私たちは2つの方法で成功の道筋を立てることに着手しました。

  • デフォルトのNext.jsエクスペリエンスは、静的な404ページを生成します。
  • 404ページをカスタマイズした場合でも、静的なページになるようにします。

この機能は完全に後方互換性があり、現在カスタムのpages/_error.jsがある場合でも、pages/404.jsを追加するまでは404ページに引き続き使用されます。

デフォルトで静的な404ページ

アプリケーションにカスタムのpages/_error.jsページがない場合、Next.jsは自動的に404ページを静的に生成し、404を提供する必要があるときにそれを使用します。これは自動的に行われ、変更は不要です。

pages/404.jsを使用したカスタム404ページ

デフォルトの404ページを上書きするには、pages/404.jsを作成できます。これはビルド時に自動的に静的に最適化されます。このページは、アプリケーションに404ページがある場合、404をレンダリングするためにpages/_error.jsの代わりに使用されます。

pages/404.js
export default () => <h1>This is the 404 page</h1>;

32KB以上**小さくなったランタイム (15KB以上Gzip)**

Next.jsは、設定なしでReact自体と同じブラウザをサポートしています。これにはInternet Explorer 11 (IE11) およびすべての主要なブラウザ (Edge、Firefox、Chrome、Safari、Operaなど) が含まれます。

この互換性の一環として、アプリケーションはIE11互換になるようにコンパイルされます。これにより、ES6+の構文機能、Async/Await、Object Rest/Spread Propertiesなどを、設定なしで安全に使用できます。

このコンパイルプロセスの一部には、必要な機能ポリフィル (例: Array.fromSymbol) を透過的に注入することも含まれます。しかし、これらのポリフィルは、ほとんどの場合IE11をサポートするために、ウェブトラフィックの10%未満でしか必要ありません。

Next.js 9.3以降、Next.jsはレガシーブラウザをサポートするために必要なポリフィルを自動的にロードし、これらのレガシーブラウザでのみポリフィルをロードします。

実際には、これによりユーザーの90%+の**初回ロード**サイズから32KB以上が削減されます。

これらのサイズ削減は、より多くのブラウザ機能に依存する大規模なアプリケーションではさらに大きくなります。

この最適化は完全に自動で行われ、活用するためにアプリケーションの変更は一切不要です!

コミュニティ

Next.jsの採用が引き続き拡大していることを大変嬉しく思います。

  • 927人を超える独立したコントリビューターがいました。
  • GitHubでは、このプロジェクトは**46,600回**以上スターを獲得しています。
  • examplesディレクトリには**226**を超える例があります。

Next.jsコミュニティは現在**15,250人**以上のメンバーがいます。コミュニティはGitHub discussionsで利用可能になり、コミュニティが質問したり議論したりする新しい場所となりました!参加しましょう!

このリリースを形成するのに役立ったコミュニティとすべての外部からのフィードバックと貢献に感謝します。

新しいデータ取得メソッドに関して多大なフィードバックを提供してくれたJeff Escalanteに特に感謝いたします。

このリリースに貢献してくださったすべての方々に心より感謝いたします:@arcanis、@lgordey、@ijjk、@martpie、@jaywink、@fabianishere、@dijs、@TheRusskiy、@quinnturner、@timneutkens、@lfades、@vvo、@adithwip、@rafaelalmeidatk、@bmathews、@Spy-Seth、@EvgeniyKumachev、@chibicode、@piglovesyou、@HaNdTriX、@Timer、@janicklas-ralph、@devknoll、@prateekbh、@ethanryan、@MoOx、@rifaidev、@msweeneydev、@motiko、そして@balazsorban44、ありがとうございました!