NetlifyとNext.jsでかさばるビルドを分解する
公開: 2022-03-10この記事は、世界中から集まった素晴らしい才能の多様なグループであり、生産性を高めるWeb開発者向けのプラットフォームを提供するNetlifyの親愛なる友人たちによって親切にサポートされています。 ありがとう!
静的に生成されたWebサイトを操作する際の最大の問題の1つは、アプリの成長に伴ってビルドが徐々に遅くなることです。 これは、スタックがある時点で直面する避けられない問題であり、使用している製品の種類に応じて、さまざまなポイントから発生する可能性があります。
たとえば、デプロイアーティファクトを生成するときにアプリに複数のページ(ビュー、ルート)がある場合、それらの各ルートはファイルになります。 次に、数千に達すると、事前に計画する必要なしに、いつ展開できるのか疑問に思い始めます。 このシナリオは、eコマースプラットフォームやブログで一般的です。これらはすでにWebの大部分を占めていますが、すべてではありません。 ただし、考えられるボトルネックはルートだけではありません。
リソースを大量に消費するアプリも、最終的にはこのターニングポイントに到達します。 多くの静的ジェネレーターは、最高のユーザーエクスペリエンスを保証するために、アセットの最適化を実行します。 ビルドの最適化(インクリメンタルビルド、キャッシング、すぐにそれらに到達します)がないと、これも最終的には管理できなくなります。Webサイト内のすべてのイメージを調べて、サイズ変更、削除、新しいファイルの作成を何度も繰り返すことを検討してください。 そして、すべてが完了したら、Jamstackがコンテンツ配信ネットワークのエッジからアプリを提供することを忘れないでください。 そのため、コンパイルされたサーバーからネットワークのエッジに移動する必要があります。
さらに、データは動的であることが多いという事実もあります。つまり、アプリをビルドしてデプロイすると、数秒、数分、さらには1時間かかる場合があります。 その間、世界は回転し続けており、他の場所からデータをフェッチしている場合、アプリは古くなるはずです。 受け入れられない! もう一度ビルドして更新してください!
一度ビルドし、必要に応じて更新します
かさばるビルドを解決することは、基本的にすべてのJamstackプラットフォーム、フレームワーク、またはサービスでしばらくの間頭に浮かびました。 多くのソリューションは、インクリメンタルビルドを中心に展開しています。 実際には、これは、ビルドが現在のデプロイメントとの違いと同じくらいかさばることを意味します。
ただし、 diffアルゴリズムの定義は簡単な作業ではありません。 エンドユーザーがこの改善から実際に利益を得るには、考慮しなければならないキャッシュ無効化戦略があります。 簡単に言うと、変更されていないページまたはアセットのキャッシュを無効にしたくないということです。
Next.jsは、インクリメンタル静的再生( ISR )を考案しました。 本質的には、ルートごとに再構築する頻度を宣言する方法です。 内部的には、サーバー側での多くの作業が簡素化されます。 すべてのルート(動的かどうかに関係なく)は特定の時間枠でそれ自体を再構築し、すべてのビルドでキャッシュを無効にするというJamstackの公理に完全に適合します。 これをmax-age
ヘッダーと考えてください。ただし、Next.jsアプリのルート用です。
アプリケーションを開始するには、ISRは構成プロパティだけです。 ルートコンポーネント( /pages
ディレクトリ内)で、 getStaticProps
メソッドに移動し、戻りオブジェクトにrevalidate
キーを追加します。
export async function getStaticProps() { const { limit, count, pokemons } = await fetchPokemonList() return { props: { limit, count, pokemons, }, revalidate: 3600 // seconds } }
上記のスニペットは、私のページが1時間ごとに再構築され、より多くのポケモンを表示するためにフェッチすることを確認します。
時々(新しいデプロイメントを発行するときに)バルクビルドを取得します。 ただし、これにより、コンテンツをコードから切り離すことができます。コンテンツをコンテンツ管理システム(CMS)に移動することで、アプリケーションのサイズに関係なく、数秒で情報を更新できます。 タイプミスを更新するためのWebhookに別れを告げます!
オンデマンドビルダー
Netlifyは最近、Next.jsのISRをサポートするためのアプローチであるオンデマンドビルダーをリリースしましたが、EleventyやNuxtなどのフレームワーク間でも機能します。 前回のセッションでは、ISRがビルド時間の短縮に向けた大きな一歩であり、ユースケースのかなりの部分に対応していることを確認しました。 それにもかかわらず、警告がありました:
- 継続的デプロイに基づいて完全に構築されます。
インクリメンタルステージは、展開後およびデータに対してのみ発生します。 コードを段階的に出荷することはできません - インクリメンタルビルドは時間の産物です。
キャッシュは時間ベースで無効になります。 そのため、コードで設定されている再検証期間によっては、不要なビルドが発生したり、必要な更新に時間がかかる場合があります。
Netlifyの新しいデプロイメントインフラストラクチャにより、開発者は、アプリのどの部分がデプロイメント上に構築され、どの部分が延期されるか(およびそれらがどのように延期されるか)を決定するロジックを作成できます。
- 致命的
アクションは必要ありません。 デプロイするものはすべて、プッシュに基づいて構築されます。 - 延期
アプリの特定の部分はデプロイ時にビルドされません。最初のリクエストが発生するたびにオンデマンドでビルドされるように延期され、そのタイプの他のリソースとしてキャッシュされます。
オンデマンドビルダーの作成
まず、プロジェクトにdevDependency
としてnetlify/functionsパッケージを追加します。
yarn add -D @netlify/functions
それが完了すると、新しいNetlify関数を作成するのと同じです。 それらに特定のディレクトリを設定していない場合は、 netlify/functions/
に進み、ビルダーに任意の名前のファイルを作成します。
import type { Handler } from '@netlify/functions' import { builder } from '@netlify/functions' const myHandler: Handler = async (event, context) => { return { statusCode: 200, body: JSON.stringify({ message: 'Built on-demand! ' }), } } export const handler = builder(myHandler)
上記のスニペットからわかるように、オンデマンドビルダーは、ハンドラーをbuilder()
メソッド内にラップするため、通常のNetlify関数から分離されます。 このメソッドは、関数をビルドタスクに接続します。 そして、必要な場合にのみ、アプリケーションの一部をビルドのために延期するために必要なのはそれだけです。 最初から小さなインクリメンタルビルド!
NetlifyのNext.js
NetlifyでNext.jsアプリを構築するには、一般的にエクスペリエンスを向上させるために追加する必要のある2つの重要なプラグインがあります。NetlifyプラグインキャッシュNext.jsとEssentialNext-on-Netlifyです。 前者はNextJSをより効率的にキャッシュするため、自分で追加する必要がありますが、後者はNext.jsアーキテクチャの構築方法を少し調整して、Netlifyのアーキテクチャに適合させ、Netlifyが識別できるすべての新しいプロジェクトでデフォルトで利用できるようにします。 Next.jsを使用します。
Next.jsを使用したオンデマンドビルダー
パフォーマンスの構築、パフォーマンスの展開、キャッシング、開発者エクスペリエンス。 これらはすべて非常に重要なトピックですが、それは多くのことであり、適切に設定するには時間がかかります。 次に、ユーザーエクスペリエンスではなく開発者エクスペリエンスに焦点を当てることについての古い議論に行き着きます。 それは物事が忘れられるためにバックログの隠された場所に行く時間です。 あまり。
Netlifyはあなたを取り戻しました。 ほんの数ステップで、Next.jsアプリでJamstackの全機能を活用できます。 袖をまくり上げて、すべてをまとめる時が来ました。
事前レンダリングされたパスの定義
Next.js内で静的生成を使用したことがある場合は、おそらくgetStaticPaths
メソッドについて聞いたことがあるでしょう。 このメソッドは、動的ルート(広範囲のページをレンダリングするページテンプレート)を対象としています。 このメソッドの複雑さにあまりこだわる必要はありませんが、戻りタイプは2つのキーを持つオブジェクトであることに注意することが重要です。たとえば、概念実証では、これは[ポケモン]動的ルートファイルになります。
export async function getStaticPaths() { return { paths: [], fallback: 'blocking', } }
-
paths
は、事前にレンダリングされるこのルートに一致するすべてのパスを実行するarray
です。 -
fallback
には、ブロッキング、true
、またはfalse
の3つの可能な値があります。
この場合、 getStaticPaths
は次のことを決定しています。
- パスは事前にレンダリングされません。
- このルートが呼び出されるたびに、フォールバックテンプレートは提供されず、ページがオンデマンドでレンダリングされ、ユーザーが待機し続け、アプリが他のことを実行できないようにします。
オンデマンドビルダーを使用する場合は、フォールバック戦略がアプリの目標を満たしていることを確認してください。公式のNext.jsドキュメント:フォールバックドキュメントは非常に便利です。
オンデマンドビルダーの前は、 getStaticPaths
は少し異なっていました。
export async function getStaticPaths() { const { pokemons } = await fetchPkmList() return { paths: pokemons.map(({ name }) => ({ params: { pokemon: name } })), fallback: false, } }
意図したすべてのポケモンページのリストを収集し、すべてのpokemon
オブジェクトをポケモン名のstring
にマップし、それを保持する{ params }
オブジェクトをgetStaticProps
に転送していました。 ルートが一致しなかった場合、Next.jsに404: Not Found
ページをスローさせたいため、 fallback
はfalse
に設定されました。
Netlifyにデプロイされている両方のバージョンを確認できます。
- オンデマンドビルダーを使用:コード、ライブ
- 完全に静的に生成:コード、ライブ
コードはGithubでもオープンソースであり、自分で簡単にデプロイしてビルド時間を確認できます。 そして、このキューで、次のトピックにスライドします。
ビルド時間
上記のように、前のデモは実際には概念実証であり、測定できない場合、本当に良いことも悪いこともありません。 私たちの小さな研究のために、私はPokeAPIに行き、すべてのポケモンを捕まえることにしました。
再現性を高めるために、リクエストの上限を設定しました( 1000
まで)。 これらは実際にはすべてAPI内にあるわけではありませんが、いずれかの時点で更新されるかどうかに関係なく、すべてのビルドでページ数が同じになるように強制します。
export const fetchPkmList = async () => { const resp = await fetch(`${API}pokemon?limit=${LIMIT}`) const { count, results, }: { count: number results: { name: string url: string }[] } = await resp.json() return { count, pokemons: results, limit: LIMIT, } }
そして、Netlifyへの別々のブランチで両方のバージョンを起動しました。プレビューデプロイのおかげで、基本的に同じ環境で共存できます。 両方の方法の違いを実際に評価するために、ODBアプローチは極端であり、その動的ルート用に事前にレンダリングされたページはありませんでした。 実際のシナリオでは推奨されませんが(トラフィックの多いルートを事前にレンダリングする必要があります)、このアプローチで達成できるビルド時のパフォーマンス向上の範囲を明確に示しています。
ストラテジー | ページ数 | 資産の数 | ビルド時間 | 総デプロイ時間 |
---|---|---|---|---|
完全に静的に生成 | 1002 | 1005 | 2分32秒 | 4分15秒 |
オンデマンドビルダー | 2 | 0 | 52秒 | 52秒 |
私たちの小さなPokeDexアプリのページはかなり小さく、画像アセットは非常にスリムですが、デプロイ時間の向上は非常に重要です。 アプリに中程度から大量のルートがある場合は、ODB戦略を検討する価値があります。
これにより、デプロイが高速になり、信頼性が向上します。 パフォーマンスヒットは最初のリクエストでのみ発生し、後続のリクエスト以降、レンダリングされたページはEdgeにキャッシュされ、完全に静的に生成されたものとまったく同じパフォーマンスになります。
未来:分散型永続レンダリング
同じ日に、オンデマンドビルダーが発表され、早期アクセスが開始されました。Netlifyは、分散永続レンダリング(DPR)に関するコメントのリクエストも公開しました。
DPRは、オンデマンドビルダーの次のステップです。 このような非同期のビルド手順を利用し、実際に更新されるまでアセットをキャッシュすることで、より高速なビルドを利用します。 10kページのWebサイトのフルビルドはもうありません。 DPRを使用すると、開発者は、堅実なキャッシュとオンデマンドビルダーを使用して、システムの構築と展開を完全に制御できます。
このシナリオを想像してみてください。eコマースWebサイトには1万の製品ページがあります。これは、展開用のアプリケーション全体を構築するのに約2時間かかることを意味します。 これがどれほど苦痛であるかを議論する必要はありません。
DPRを使用すると、すべてのデプロイでビルドする上位500ページを設定できます。 私たちの最も重いトラフィックページは常にユーザーのために用意されています。 しかし、私たちはお店です。つまり、毎秒が重要です。 したがって、他の9500ページについては、ビルド後のフックを設定してビルダーをトリガーできます。残りのページを非同期でデプロイし、すぐにキャッシュします。 ユーザーが怪我をすることはなく、ウェブサイトは可能な限り最速のビルドで更新され、キャッシュに存在しなかった他のすべてのものが保存されました。
結論
この記事の議論のポイントの多くは概念的なものであり、実装を定義する必要がありますが、Jamstackの将来に興奮しています。 コミュニティとして私たちが行っている進歩は、エンドユーザーエクスペリエンスを中心に展開しています。
分散永続レンダリングについてどう思いますか? アプリケーションでオンデマンドビルダーを試しましたか? コメントで詳しく教えてください。またはTwitterで電話してください。 私は本当に興味があります!
参考文献
- 「Next.jsを使用したインクリメンタル静的再生(ISR)の完全ガイド」Lee Robinson
- 「オンデマンドビルダーを使用したNetlifyの大規模サイト向けの高速ビルド」、Asavari Tayal、Netlifyブログ
- 「分散永続レンダリング:より高速なビルドのための新しいJamstackアプローチ」、Matt Biilmann、Netlifyブログ
- 「DistributedPersistentRendering(DPR)」、Cassidy Williams、GitHub